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
@@ -43,6 +44,7 @@
 - Arlo O'Keeffe
 - Artem Pelenitsyn
 - Artyom Kazak
+- Aslak Hellesøy
 - Asliddinbek Azizovich
 - B. Scott Michel
 - Bastien Dumont
@@ -87,6 +89,7 @@
 - Christoffer Sawicki
 - Christophe Dervieux
 - Christopher Kenny
+- Clar Fon
 - Clare Macrae
 - Clint Adams
 - Conal Elliott
@@ -125,6 +128,7 @@
 - Eric Kow
 - Eric Schrijver
 - Eric Seidel
+- Erik Demaine
 - Erik Post
 - Erik Rask
 - Ethan Riley
@@ -151,6 +155,7 @@
 - Fyodor Sheremetyev
 - Gabor Pali
 - Gabriel Lewertowski
+- Gaurav Vijay Jadhav
 - Gavin Beatty
 - George Stagg
 - Georgi Lyubenov
@@ -250,6 +255,7 @@
 - Konstantin Zudov
 - Kristof Bastiaensen
 - Krystof Beuermann
+- Kyohei Takahashi
 - Lars-Dominik Braun
 - Laurent P. René de Cotret
 - Lawrence Chonavel
@@ -359,6 +365,7 @@
 - Puneeth Chaganti
 - Quinn
 - R. N. West
+- Raffaele Mancuso
 - Ralf Stephan
 - Raniere Silva
 - Raymond Berger
@@ -366,6 +373,7 @@
 - Recai Oktaş
 - Repetitive
 - Reuben Thomas
+- Robert Szarka
 - Robertas
 - Rowan Rodrik van der Molen
 - Roland Hieber
@@ -378,6 +386,7 @@
 - Salim B
 - Sam S. Almahri
 - Sam May
+- Samuel Huang
 - Samuel Tardieu
 - Saumel Lemmenmeier
 - Santiago Zarate
@@ -467,6 +476,7 @@
 - Yoan Blanc
 - You Jiangbin
 - Yuchen Pei
+- Yusuf Efe
 - Zihang Chen
 - 3w36zj6
 - arcnmx
@@ -524,6 +534,7 @@
 - priiduonu
 - qerub
 - quasicomputational
+- rca-umb
 - reptee
 - ricnorr
 - robabla
@@ -549,4 +560,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-10-07
 ---
 
 # Synopsis
@@ -251,6 +251,7 @@
     - `endnotexml` ([EndNote XML bibliography])
     - `epub` ([EPUB])
     - `fb2` ([FictionBook2] e-book)
+    - `fodt` (flat [OpenDocument text document][ODT])
     - `gfm` ([GitHub-Flavored Markdown]),
       or the deprecated and less accurate `markdown_github`;
       use [`markdown_github`](#markdown-variants) only
@@ -327,6 +328,7 @@
     - `epub` or `epub3` ([EPUB] v3 book)
     - `epub2` (EPUB v2)
     - `fb2` ([FictionBook2] e-book)
+    - `fodt` (flat [OpenDocument text document][ODT])
     - `gfm` ([GitHub-Flavored Markdown]),
       or the deprecated and less accurate `markdown_github`;
       use [`markdown_github`](#markdown-variants) only
@@ -1146,7 +1148,10 @@
 `--link-images[=true|false]`
 
 :   Include links to images instead of embedding the images in ODT.
-    (This option currently only affects ODT output.)
+    (This option currently only affects `odt` and `fodt` output.)
+    This is worth considering for `fodt`, where images have to be
+    base64-encoded and so take about a third more space than in the
+    compressed `odt` package.
 
 `--html-q-tags[=true|false]`
 
@@ -1408,6 +1413,12 @@
         Then open `custom-reference.odt` in LibreOffice, modify
         the styles as you wish, and save the file.
 
+        `fodt` output uses the same reference documents as `odt`;
+        the reference is still a zipped `.odt`, which is flattened
+        along with the generated content.  Note that an automatic
+        style of the reference document is dropped, with a warning,
+        if the generated content defines a style of the same name.
+
     PowerPoint
 
     :   Templates included with Microsoft PowerPoint 2013 (either with
@@ -2937,17 +2948,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 +2975,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`
@@ -2985,6 +3003,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
 
 `classoption`
@@ -3550,6 +3603,16 @@
 :   color for external links, internal links, and citation links,
     respectively: expects a hexadecimal color code
 
+`csl-entry-spacing`
+:   entry spacing between citeproc-generated bibliography entries
+    (with a unit, e.g. `1em`). This will be set automatically
+    based on the CSL style but can be overridden.
+
+`csl-hanging-indent`
+:   amount of hanging indent in citeproc-generated bibliography entries
+    (with a unit, e.g. `1.5em`). This will be set automatically
+    based on the CSL style but can be overridden.
+
 ### Variables for ms
 
 `fontfamily`
@@ -6588,7 +6651,7 @@
 
         Spin wave dispersion on the <span class="nocase">nm</span> scale
 
-### Conference Papers, Published vs. Unpublished
+### Conference papers, published vs. unpublished
 
 For a formally published conference paper, use the biblatex entry type
 `inproceedings` (which will be mapped to CSL `paper-conference`).
@@ -6607,7 +6670,6 @@
 which describes the publisher's location; do not use the latter
 for an unpublished conference paper.
 
-
 ## Specifying a citation style
 
 Citations and references can be formatted using any style supported by the
@@ -7787,11 +7849,11 @@
 
 # Custom Styles
 
-Custom styles can be used in the docx, odt and ICML formats.
+Custom styles can be used in the docx, odt, fodt and ICML formats.
 
 ## Output
 
-By default, pandoc's odt, docx and ICML output applies a predefined set of
+By default, pandoc's odt, fodt, docx and ICML output applies a predefined set of
 styles for blocks such as paragraphs and block quotes, and uses largely
 default formatting (italics, bold) for inlines. This will work for most
 purposes, especially alongside a [reference doc](#option--reference-doc) file.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -54,6 +54,8 @@
 - `fb2`
   ([FictionBook2](http://www.fictionbook.org/index.php/Eng:XML_Schema_Fictionbook_2.1)
   e-book)
+- `fodt` (flat [OpenDocument text
+  document](https://en.wikipedia.org/wiki/OpenDocument))
 - `gfm` ([GitHub-Flavored
   Markdown](https://help.github.com/articles/github-flavored-markdown/)),
   or the deprecated and less accurate `markdown_github`; use
@@ -161,6 +163,8 @@
 - `fb2`
   ([FictionBook2](http://www.fictionbook.org/index.php/Eng:XML_Schema_Fictionbook_2.1)
   e-book)
+- `fodt` (flat [OpenDocument text
+  document](https://en.wikipedia.org/wiki/OpenDocument))
 - `gfm` ([GitHub-Flavored
   Markdown](https://help.github.com/articles/github-flavored-markdown/)),
   or the deprecated and less accurate `markdown_github`; use
diff --git a/benchmark/benchmark-pandoc.hs b/benchmark/benchmark-pandoc.hs
--- a/benchmark/benchmark-pandoc.hs
+++ b/benchmark/benchmark-pandoc.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
 {-
 Copyright (C) 2012-2024 John MacFarlane <jgm@berkeley.edu>
 
@@ -18,6 +19,8 @@
 -}
 import Text.Pandoc
 import Text.Pandoc.MIME
+import Text.Pandoc.Shared (stringify, stringifyInlines)
+import Text.Pandoc.MediaBag (mediaItems)
 import Control.DeepSeq (force)
 import Control.Monad.Except (throwError)
 import qualified Text.Pandoc.UTF8 as UTF8
@@ -26,43 +29,62 @@
 import Test.Tasty.Bench
 -- import Gauge
 import qualified Data.ByteString.Lazy as BL
-import Data.Maybe (mapMaybe)
 import Data.List (sortOn)
 import Text.Pandoc.Format (FlavoredFormat(..))
+import System.IO.Temp
+import Data.Maybe
 
-readerBench :: Pandoc
+readerBench :: [(FilePath, MimeType, BL.ByteString)]
+            -> Pandoc
             -> T.Text
             -> Maybe Benchmark
-readerBench _ name
+readerBench _ _ name
   | name `elem` ["bibtex", "biblatex", "csljson"] = Nothing
-readerBench doc name = either (const Nothing) Just $
+readerBench imgs doc name = either (const Nothing) Just $
   runPure $ do
     (rdr, rexts) <- getReader $ FlavoredFormat name mempty
     (wtr, wexts) <- getWriter $ FlavoredFormat name mempty
-    tmpl <- Just <$> compileDefaultTemplate name
+    tpl <- compileDefaultTemplate name 
     case (rdr, wtr) of
       (TextReader r, TextWriter w) -> do
         inp <- w def{ writerWrapText = WrapAuto
                     , writerExtensions = wexts
-                    , writerTemplate = tmpl } doc
-        return $ bench (T.unpack name) $
-          nf (either (error . show) id . runPure . r def) inp
+                    , writerTemplate = Just tpl } doc
+        return $ bench (T.unpack name)
+               $ nf (\x -> either (error . show) id $
+                       runPure $ do
+                         mapM_ (\(fp,mt,bs) -> insertMedia fp (Just mt) bs) imgs
+                         r def x)
+                    inp
       (ByteStringReader r, ByteStringWriter w) -> do
         inp <- w def{ writerWrapText = WrapAuto
                     , writerExtensions = wexts
-                    , writerTemplate = tmpl } doc
-        return $ bench (T.unpack name) $
-          nf (either (error . show) id .
-                runPure . r def{readerExtensions = rexts}) inp
+                    , writerTemplate = Just tpl } doc
+        return $ bench (T.unpack name)
+               $ nf (\x -> either (error . show) id $
+                       runPure $ do
+                         mapM_ (\(fp,mt,bs) -> insertMedia fp (Just mt) bs) imgs
+                         r def{readerExtensions = rexts} x)
+                    inp
       _ -> throwError $ PandocSomeError $ "text/bytestring format mismatch: "
                            <> name
 
-getImages :: IO [(FilePath, MimeType, BL.ByteString)]
-getImages = do
-  ll <- B.readFile "test/lalune.jpg"
-  mv <- B.readFile "test/movie.jpg"
-  return [("lalune.jpg", "image/jpg", BL.fromStrict ll)
-         ,("movie.jpg", "image/jpg", BL.fromStrict mv)]
+getSample :: FilePath -> IO (Pandoc, [(FilePath, MimeType, BL.ByteString)])
+getSample fp = do
+  inp <- UTF8.toText <$> B.readFile fp
+  let opts = def
+  doc' <- runIOorExplode $ do
+            (_, rexts) <- getReader $ FlavoredFormat "markdown" mempty
+            readMarkdown
+              opts{ readerExtensions = enableExtension Ext_rebase_relative_paths
+                                              rexts }
+              [(fp, inp)]
+  withSystemTempDirectory "pandoc-bench-resources" $ \tmpdir ->
+    runIOorExplode $ do
+      doc <- extractMedia tmpdir doc'
+      fillMediaBag doc
+      items <- mediaItems <$> getMediaBag
+      pure $! (doc, items)
 
 writerBench :: [(FilePath, MimeType, BL.ByteString)]
             -> Pandoc
@@ -73,33 +95,50 @@
 writerBench imgs doc name = either (const Nothing) Just $
   runPure $ do
     (wtr, wexts) <- getWriter $ FlavoredFormat name mempty
+    tpl <- compileDefaultTemplate name
+    let opts = def{ writerExtensions = wexts, writerTemplate = Just tpl }
     case wtr of
       TextWriter writerFun ->
         return $ bench (T.unpack name)
                $ nf (\d -> either (error . show) id $
                        runPure $ do
                          mapM_ (\(fp,mt,bs) -> insertMedia fp (Just mt) bs) imgs
-                         writerFun def{ writerExtensions = wexts} d)
+                         writerFun opts d)
                     doc
       ByteStringWriter writerFun ->
         return $ bench (T.unpack name)
                $ nf (\d -> either (error . show) id $
                        runPure $ do
                          mapM_ (\(fp,mt,bs) -> insertMedia fp (Just mt) bs) imgs
-                         writerFun def{ writerExtensions = wexts} d)
+                         writerFun opts 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"
-  let opts = def
-  let doc = either (error . show) force $ runPure $ readMarkdown opts inp
-  defaultMain
-    [ env getImages $ \imgs ->
-      bgroup "writers" $ mapMaybe (writerBench imgs doc . fst)
-                         (sortOn fst
-                           writers :: [(T.Text, Writer PandocPure)])
-    , bgroup "readers" $ mapMaybe (readerBench doc . fst)
-                         (sortOn fst
-                           readers :: [(T.Text, Reader PandocPure)])
+  (doc, imgs) <- getSample "benchmark/sample.md"
+  defaultMain $
+    [ bgroup "writers" $ mapMaybe (writerBench imgs doc . fst)
+                            (sortOn fst
+                              writers :: [(T.Text, Writer PandocPure)])
+    , bgroup "readers" $ mapMaybe (readerBench imgs 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/benchmark/lalune.jpg b/benchmark/lalune.jpg
new file mode 100644
Binary files /dev/null and b/benchmark/lalune.jpg differ
diff --git a/benchmark/movie.jpg b/benchmark/movie.jpg
new file mode 100644
Binary files /dev/null and b/benchmark/movie.jpg differ
diff --git a/benchmark/sample.md b/benchmark/sample.md
new file mode 100644
--- /dev/null
+++ b/benchmark/sample.md
@@ -0,0 +1,1597 @@
+% Pandoc Test Suite
+% John MacFarlane; Anonymous
+% July 17, 2006
+
+This is a set of tests for pandoc.  Most of them are adapted from
+John Gruber's markdown test suite.
+
+-----
+
+# Headers
+
+## Level 2 with an [embedded link](/url)
+
+### Level 3 with *emphasis*
+
+#### Level 4
+
+##### Level 5
+
+Level 1
+=======
+
+Level 2 with *emphasis*
+-----------------------
+
+### Level 3
+with no blank line
+
+Level 2
+-------
+with no blank line
+
+----------
+
+# Paragraphs
+
+Here's a regular paragraph.
+
+In Markdown 1.0.0 and earlier. Version
+8. This line turns into a list item.
+Because a hard-wrapped line in the
+middle of a paragraph looked like a
+list item.
+
+Here's one with a bullet.
+* criminey.
+
+There should be a hard line break
+here.
+
+---
+
+# Block Quotes
+
+E-mail style:
+
+> This is a block quote.
+> It is pretty short.
+
+> Code in a block quote:
+>
+>     sub status {
+>         print "working";
+>     }
+>
+> A list:
+>
+> 1. item one
+> 2. item two
+>
+> Nested block quotes:
+>
+> > nested
+>
+>>  nested
+>
+
+This should not be a block quote: 2
+> 1.
+
+And a following paragraph.
+
+* * * *
+
+# Code Blocks
+
+Code:
+
+    ---- (should be four hyphens)
+
+    sub status {
+        print "working";
+    }
+
+	this code block is indented by one tab
+
+And:
+
+		this code block is indented by two tabs
+
+    These should not be escaped:  \$ \\ \> \[ \{
+
+___________
+
+# Lists
+
+## Unordered
+
+Asterisks tight:
+
+*	asterisk 1
+*	asterisk 2
+*	asterisk 3
+
+Asterisks loose:
+
+*	asterisk 1
+
+*	asterisk 2
+
+*	asterisk 3
+
+Pluses tight:
+
++	Plus 1
++	Plus 2
++	Plus 3
+
+Pluses loose:
+
++	Plus 1
+
++	Plus 2
+
++	Plus 3
+
+Minuses tight:
+
+-	Minus 1
+-	Minus 2
+-	Minus 3
+
+Minuses loose:
+
+-	Minus 1
+
+-	Minus 2
+
+-	Minus 3
+
+## Ordered
+
+Tight:
+
+1.	First
+2.	Second
+3.	Third
+
+and:
+
+1. One
+2. Two
+3. Three
+
+Loose using tabs:
+
+1.	First
+
+2.	Second
+
+3.	Third
+
+and using spaces:
+
+1. One
+
+2. Two
+
+3. Three
+
+Multiple paragraphs:
+
+1.	Item 1, graf one.
+
+	Item 1. graf two. The quick brown fox jumped over the lazy dog's
+	back.
+
+2.	Item 2.
+
+3.	Item 3.
+
+## Nested
+
+*	Tab
+	*	Tab
+		*	Tab
+
+Here's another:
+
+1. First
+2. Second:
+	* Fee
+	* Fie
+	* Foe
+3. Third
+
+Same thing but with paragraphs:
+
+1. First
+
+2. Second:
+
+	* Fee
+	* Fie
+	* Foe
+
+3. Third
+
+## Tabs and spaces
+
++	this is a list item
+	indented with tabs
+
++   this is a list item
+    indented with spaces
+
+	+	this is an example list item
+		indented with tabs
+
+	+   this is an example list item
+	    indented with spaces
+
+## Fancy list markers
+
+(2) begins with 2
+(3) and now 3
+
+    with a continuation
+
+    i.  sublist with roman numerals
+        starting with i.
+    ii. more items
+        (A)  a subsublist
+        (B)  a subsublist
+
+Nesting:
+
+D.  Upper Alpha
+    I.  Upper Roman.
+        (1) Decimal start with 1
+            a)  Lower alpha with paren
+
+Autonumbering:
+
+ #.  Autonumber.
+ #.  More.
+     #.  Nested.
+
+Should not be a list item:
+
+M.A. 2007
+
+B. Williams
+
+  *   *   *   *   *
+
+# Definition Lists
+
+Tight using spaces:
+
+apple
+:   red fruit
+
+orange
+:   orange fruit
+
+banana
+:   yellow fruit
+
+Tight using tabs:
+
+apple
+:	red fruit
+
+orange
+:	orange fruit
+
+banana
+:	yellow fruit
+
+Loose:
+
+apple
+
+:   red fruit
+
+orange
+
+:   orange fruit
+
+banana
+
+:   yellow fruit
+
+Multiple blocks with italics:
+
+*apple*
+
+:   red fruit
+
+    contains seeds,
+    crisp, pleasant to taste
+
+*orange*
+
+:   orange fruit
+
+        { orange code block }
+
+    > orange block quote
+
+Multiple definitions, tight:
+
+apple
+:   red fruit
+:   computer
+
+orange
+:   orange fruit
+:   bank
+
+Multiple definitions, loose:
+
+apple
+
+:   red fruit
+
+:   computer
+
+orange
+
+:   orange fruit
+
+:   bank
+
+Blank line after term, indented marker, alternate markers:
+
+apple
+
+  ~ red fruit
+
+  ~ computer
+
+orange
+
+  ~ orange fruit
+
+    1. sublist
+    2. sublist
+
+# HTML Blocks
+
+Simple block on one line:
+
+<div>foo</div>
+
+And nested without indentation:
+
+<div>
+<div>
+<div>
+foo
+</div>
+</div>
+<div>bar</div>
+</div>
+
+Interpreted markdown in a table:
+
+<table>
+<tr>
+<td>This is *emphasized*</td>
+<td>And this is **strong**</td>
+</tr>
+</table>
+
+<script type="text/javascript">document.write('This *should not* be interpreted as markdown');</script>
+
+Here's a simple block:
+
+<div>
+foo
+</div>
+
+This should be a code block, though:
+
+	<div>
+		foo
+	</div>
+
+As should this:
+
+	<div>foo</div>
+
+Now, nested:
+
+<div>
+ <div>
+  <div>
+  foo
+  </div>
+ </div>
+</div>
+
+This should just be an HTML comment:
+
+<!-- Comment -->
+
+Multiline:
+
+<!--
+Blah
+Blah
+-->
+
+<!--
+	This is another comment.
+-->
+
+Code block:
+
+	<!-- Comment -->
+
+Just plain comment, with trailing spaces on the line:
+
+<!-- foo -->
+
+Code:
+
+	<hr />
+
+Hr's:
+
+<hr>
+
+<hr />
+
+<hr />
+
+<hr>
+
+<hr />
+
+<hr />
+
+<hr class="foo" id="bar" />
+
+<hr class="foo" id="bar" />
+
+<hr class="foo" id="bar">
+
+-----
+
+# Inline Markup
+
+This is *emphasized*, and so _is this_.
+
+This is **strong**, and so __is this__.
+
+An *[emphasized link](/url)*.
+
+***This is strong and em.***
+
+So is ***this*** word.
+
+___This is strong and em.___
+
+So is ___this___ word.
+
+This is code: `>`, `$`, `\`, `\$`, `<html>`.
+
+~~This is *strikeout*.~~
+
+Superscripts:  a^bc^d a^*hello*^ a^hello\ there^.
+
+Subscripts: H~2~O, H~23~O, H~many\ of\ them~O.
+
+These should not be superscripts or subscripts,
+because of the unescaped spaces:  a^b c^d, a~b c~d.
+
+-----
+
+# Smart quotes, ellipses, dashes
+
+"Hello," said the spider.  "'Shelob' is my name."
+
+'A', 'B', and 'C' are letters.
+
+'Oak,' 'elm,' and 'beech' are names of trees.
+So is 'pine.'
+
+'He said, "I want to go."'  Were you alive in the
+70's?
+
+Here is some quoted '`code`' and a "[quoted link][1]".
+
+Some dashes:  one---two --- three---four --- five.
+
+Dashes between numbers: 5--7, 255--66, 1987--1999.
+
+Ellipses...and...and....
+
+-----
+
+# LaTeX
+
+- \cite[22-23]{smith.1899}
+- $2+2=4$
+- $x \in y$
+- $\alpha \wedge \omega$
+- $223$
+- $p$-Tree
+- Here's some display math:
+  $$\frac{d}{dx}f(x)=\lim_{h\to 0}\frac{f(x+h)-f(x)}{h}$$
+- Here's one that has a line break in it:  $\alpha + \omega \times x^2$.
+
+These shouldn't be math:
+
+- 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).
+- Escaped `$`:  $73 *this should be emphasized* 23\$.
+
+Here's a LaTeX table:
+
+\begin{tabular}{|l|l|}\hline
+Animal & Number \\ \hline
+Dog    & 2      \\
+Cat    & 1      \\ \hline
+\end{tabular}
+
+* * * * *
+
+# Special Characters
+
+Here is some unicode:
+
+- I hat: Î
+- o umlaut: ö
+- section: §
+- set membership: ∈
+- copyright: ©
+
+AT&T has an ampersand in their name.
+
+AT&amp;T is another way to write it.
+
+This & that.
+
+4 < 5.
+
+6 > 5.
+
+Backslash: \\
+
+Backtick: \`
+
+Asterisk: \*
+
+Underscore: \_
+
+Left brace: \{
+
+Right brace: \}
+
+Left bracket: \[
+
+Right bracket: \]
+
+Left paren: \(
+
+Right paren: \)
+
+Greater-than: \>
+
+Hash: \#
+
+Period: \.
+
+Bang: \!
+
+Plus: \+
+
+Minus: \-
+
+- - - - - - - - - - - - -
+
+# Links
+
+## Explicit
+
+Just a [URL](/url/).
+
+[URL and title](/url/ "title").
+
+[URL and title](/url/  "title preceded by two spaces").
+
+[URL and title](/url/	"title preceded by a tab").
+
+[URL and title](/url/ "title with "quotes" in it")
+
+[URL and title](/url/ 'title with single quotes')
+
+[with\_underscore](/url/with_underscore)
+
+[Email link](mailto:nobody@nowhere.net)
+
+[Empty]().
+
+## Reference
+
+Foo [bar][a].
+
+[a]: /url/
+
+With [embedded [brackets]][b].
+
+[b] by itself should be a link.
+
+Indented [once][].
+
+Indented [twice][].
+
+Indented [thrice][].
+
+This should [not][] be a link.
+
+ [once]: /url
+  [twice]: /url
+
+   [thrice]: /url
+
+    [not]: /url
+
+[b]: /url/
+
+Foo [bar][].
+
+Foo [biz](/url/ "Title with "quote" inside").
+
+  [bar]: /url/ "Title with "quotes" inside"
+
+## With ampersands
+
+Here's a [link with an ampersand in the URL][1].
+
+Here's a link with an amersand in the link text: [AT&T][2].
+
+Here's an [inline link](/script?foo=1&bar=2).
+
+Here's an [inline link in pointy braces](</script?foo=1&bar=2>).
+
+[1]: http://example.com/?foo=1&bar=2
+[2]: http://att.com/  "AT&T"
+
+## Autolinks
+
+With an ampersand: <http://example.com/?foo=1&bar=2>
+
+* In a list?
+* <http://example.com/>
+* It should.
+
+An e-mail address:  <nobody@nowhere.net>
+
+> Blockquoted: <http://example.com/>
+
+Auto-links should not occur here: `<http://example.com/>`
+
+	or here: <http://example.com/>
+
+----
+
+# Images
+
+From "Voyage dans la Lune" by Georges Melies (1902):
+
+![lalune][]
+
+   [lalune]: lalune.jpg "Voyage dans la Lune"
+
+Here is a movie ![movie](movie.jpg) icon.
+
+----
+
+# Footnotes
+
+Here is a footnote reference,[^1] and another.[^longnote]
+This should *not* be a footnote reference, because it
+contains a space.[^my note]  Here is an inline note.^[This
+is *easier* to type.  Inline notes may contain
+[links](http://google.com) and `]` verbatim characters,
+as well as [bracketed text].]
+
+> Notes can go in quotes.^[In quote.]
+
+1.  And in list items.^[In list.]
+
+[^longnote]: Here's the long note.  This one contains multiple
+blocks.
+
+    Subsequent blocks are indented to show that they belong to the
+footnote (as with list items).
+
+          { <code> }
+
+    If you want, you can indent every line, but you can also be
+    lazy and just indent the first line of each block.
+
+This paragraph should not be part of the note, as it is not indented.
+
+  [^1]: Here is the footnote.  It can go anywhere after the footnote
+  reference.  It need not be placed at the end of the document.
+
+# Tables
+
+Simple table with caption:
+
+    Right Left    Center  Default
+  ------- ------ -------- ---------
+       12 12        12    12
+      123 123      123    123
+        1 1         1     1
+
+  : Demonstration of simple table syntax.
+
+Simple table without caption:
+
+    Right Left    Center  Default
+  ------- ------ -------- ---------
+       12 12        12    12
+      123 123      123    123
+        1 1         1     1
+
+Simple table indented two spaces:
+
+    Right Left    Center  Default
+  ------- ------ -------- ---------
+       12 12        12    12
+      123 123      123    123
+        1 1         1     1
+
+  : Demonstration of simple table syntax.
+
+Multiline table with caption:
+
+  ---------------------------------------------------------------
+   Centered   Left              Right Default aligned
+    Header    Aligned         Aligned
+  ----------- ---------- ------------ ---------------------------
+     First    row                12.0 Example of a row that spans
+                                      multiple lines.
+
+    Second    row                 5.0 Here's another one. Note
+                                      the blank line between
+                                      rows.
+  ---------------------------------------------------------------
+
+  : Here's the caption. It may span multiple lines.
+
+Multiline table without caption:
+
+  ---------------------------------------------------------------
+   Centered   Left              Right Default aligned
+    Header    Aligned         Aligned
+  ----------- ---------- ------------ ---------------------------
+     First    row                12.0 Example of a row that spans
+                                      multiple lines.
+
+    Second    row                 5.0 Here's another one. Note
+                                      the blank line between
+                                      rows.
+  ---------------------------------------------------------------
+
+Table without column headers:
+
+  ----- ----- ----- -----
+     12 12     12      12
+    123 123    123    123
+      1 1       1       1
+  ----- ----- ----- -----
+
+Multiline table without column headers:
+
+  ----------- ---------- ------------ ---------------------------
+     First    row                12.0 Example of a row that spans
+                                      multiple lines.
+
+    Second    row                 5.0 Here's another one. Note
+                                      the blank line between
+                                      rows.
+  ----------- ---------- ------------ ---------------------------
+## Porro'm modi {#nemine-amet}
+
+Aut ex quas v enim ut proin dui intaminata equestrem wisi arcui
+Earum praesidia est scientiam fusce rerum abominationem:
+
+> Per duis ut eum aversionem scandalum te testimonium afflictio
+> li paucis commune et consiliarorum nisl hac apparct facunde est
+> nemo nicolae, ad desiderabilem est pede antiguo, iste qui eros
+> conferre. Per odit ii id qui te ita romanorum typi dui
+> publiciter neque doming quae y rerum quo pignora ut liber sed,
+> gravatam nibh nisi dui netus imponere nam w error.
+
+Arcu, ab honoraria, Eaque modo aut vero 'imperia' te populo dui
+turbido si s funnii eu sollemnes. Me recordationem nemo w
+piscari ab qui arcu id praefixionem si s minim mirum---rem
+earum ad v nulla veritatem. Ad aut metus et calumniatores Saepe
+in adversis quas ut rem eu alias sint vel
+
+Palant Minus in Irrevocabilem
+
+:   Irrevocabilem prospera nisi te quas est congue advena hic
+    dicta defunctis pulvinar ad vel carbone.
+
+Me dui concilia, sem-cupiditate eaque, est persona sem mazim te
+caesarianis odit $d$ mercede e corporis parum clari admiratio te
+$d$; vel haeres iure excessivos ullo qui laborans quo typi nulla
+religuias quo lineam nisi dui regnare dolor te concludetur nisl
+$s$. Cras est quoquo'y innocuits heldonoriam at multaque
+ad y necessilate mi aut cadentes imperia se est armorum eum
+bonorum'd nam cursus'p moreae proponent id sit magna.
+[Gennere @liber:culpa, a. 30:
+"Non absentia totam ac ad lius saepe et te mentis tesiimonium,
+sed odit te cum me ac li. Decursu (0) nisi nec ex est eros
+maiores $O$ ad $V$ ad...; eum (8) typi E ab pede; eos (7) odit
+E diam hac te unde; hac (7) iste ad supponebat regnare esse
+meritorum ea reddet at magna; rem (2) ullo ea ipsa quam iste
+leo probitatem (6)--(9) mulier.  Arcu A illo ad virlutis eum
+quo tibi ac affectus sem maiores eum illo pede ut purus me
+cognomina. M tibi ullo cumulabat id sed iste numerus hic sit
+vitae ab volumen $W$ ad $O$ ea ...  sed iste A fuga te ex
+fuga. V ille sed il rem nec tibi vero li.  Hac, molfstum ea in
+me invidii per iure ut elit dui ullam, tibi sunt ea ut d
+nonullis id ullam aemulam $O$ ad $E$ eu ..."]
+
+Nam arcu qui semper praeclusa dis assum te aut possimus?
+Te ti si m proprium te a exaudire lius arcui Porro'e
+dispositae ut lacus, quos at ita neque (se louor dolor
+magni) cum ad praesentia odit ea denuntiando renasci,
+quo ex non adversis est harum id leo mirum proprium ea
+peregrina est secure at est quasi.  Ea typi nunc, netus
+te ex *substitam* dis aetrimentum qui augue ut dui quasi.
+Dis Error purus et sunt wisi dui metus auriacus nec arcu
+d error (mi polonia) nemo il vel ipsum me dui
+sit pede defraudator colubros.  Ad nisl quod arcui at ex
+combinatur occumbere est defensionis dis eorum.
+Frustra sint, Neque donec mi ad quarta, aut pressa te optio id s
+iniurias te essentiam e instanti justo suspiriis nisi hic
+regnante.
+
+Saepe vero hic diam ea tempore---ex equestri in est cum at
+stilum.  Aut ex mus quas nec id erat typi eget et cras.
+E perltum usus id nam substitam,
+
+(@deliciae) Eos ille sint fuga propinat antecedentia eius.
+
+Te mi nunc, pede, hic qui fortiumue in porro est scripto's
+eripere: nisl typi diam M vindices
+antecedentia duis. Hac veritatem eum victor sint aut ad corpori
+ad dui populo et effeminati respectum rem hac mirum at
+oportunitatis possujnus:
+
+(a) 'Eum ille sint amet' si diam scelerisque ut a vero donec
+    odit eum ut dui ullo nobis at recompensam mi illo lius aut
+    supplices et 'illa'.
+(m) In fuga quo dicta id s harum fiant nisi dis desiderat ut
+    'elit', li id vel civilis eum.
+(p) Nam titulum lius modi te nisi eius W.
+
+Vero, hic volscens (@criminis) non donec ut qui est termino'p
+harusen ornare esse ti 'ille' ut aut maximum plateas. Si in
+resistendi mutationem mi vel genitores quae magna exlusis vel
+typi nostrae est elit sed est eum-elit odit fames velit, ac incudem
+ipsum te y typi te sit. Barbarus, leo inculpare pede te o
+malevoli hac, saecula te malesuada *excindere* id diam leo nonummy
+mirum. Ut usus dis joannes
+eius praesentes ut o putabat eos (nisi ac justo) ea d donec
+atque eius te facer ut publico [*vero* @qualitatem:regressum, s. 44].
+
+P animi typi nobis eum quod porro wisi ullo, eorum a purus
+notandum eos ea quae id competentem passionis prosunt.
+Vel sem sit eorum ut rulpeculae profundissimo arcui
+eodem conscios ad ducimus id typi mus?  Erat eaque utraque.
+Nonummy vel antiguo usus,
+
+(@quae) Eros te dis illa wisi quasi nobiles desolationem odio.
+
+At neque nisi est formidine nec aversionem iste habitasse, aut.
+Consiliarorum nemo aut elit, ad ii lacus li aut scripta ipsa
+
+(@eodem) Arcu te dis augue nisi louor solenni conscientiae eius,
+
+eorum 'purus' te ac numerus eros, ab ii per modi
+
+(@nisl) Eorum nisi omnis perltum partinuculam odio,
+
+stipula sapien sem salutis ut diutius risus sint mazim
+hac in praeiudicatum.
+
+Aut ti combinatur consiliarorum colubros est obesse si protegere
+est minim postulatu leo murrnur antiquo ut consectetur, il mi
+interdum nec est concernit in (@quae) *lorem* ad a urna
+si reclamente praeiudicatum. Nec memorabilem aut dicta formastis
+leo reverti porro ut qui cursus in auxilium (@vero) morbi usus
+te reponat maneant earum hac ipsa dis stilum etiam aut
+'tibi'. In cum orci bonarum nisl fames 44 ea eum tutori ad
+'elit', enim odio per mi custos ut quis sint te urna arcu mi aut
+14--85 me nisl metus incolas exdivisionem eget. Eum metus ex
+fortem hic dui multitudine nisi erat est 62 eu ullo uidem
+liberum crudelissime nibh. Ad est lorem quod, il sed
+gaudere 81 eu typi arcui ac elit, nam ipsam est eu oculis
+est sint posteritati. Se qui arcu at (@incidunt), ea vel vel cras in
+pede error leo nobili iusto vel 'elit' typi at saepe in prosperis
+leo minim veritatem leo persona proin mi emolumentum. Est in aut
+quos in (@quae), ti parum, ea ea. Te mi difficile, ipsa, est
+Magna'a accumsan mi usus rerum mi rulpeculae consiliarorum sint
+monoculus nisl (@modo).[^aspirat]
+
+[^aspirat]:
+    @alacritate:erubescam [ea. 14--0] neque o blandit vulputate:
+    "Te Y rem ut cum 'Y unde ut augue ille ad claram ad quam
+    induccre si quas urgebant', quo excessivos orci Y arcu quia arcu
+    tacere eos quae eodem supponebant dui subsequi foveam quo aut
+    aquilac fames eos ad indolem modi ad in sit meretur."
+
+
+
+## Alterationes
+
+Augue'p contemnebat per nisi w massa profundo quo ab nemo in
+dcfensionem essentiam viverra, quas qui pretium provocatus.
+Ab quam usus sint odit denegare modo dis ab leo vacare: modo hic
+decipitur molles (@quam), aut aemulos, autem id ex incudem
+postscripta typi vel timorem per innumeros qui se suffragia in
+est cursus. Diam dicta ad mus morbi porro odit nisi?
+
+Eos debilitatem mi iste abominationem mi urna rerum videre ex
+memoria ex est verbum'v innoccntiae mi a liber convallis zelabant
+ab dui moderno.  Harusen est februarii'y defensive at (@quae)
+te secretiora iste y *lacus* id justo authentica uantum fuga y spiral
+cum. Ad elit erat si ullo victor mi Apparet SE, aut
+sit ea leo enim ea ad vestri eu dis Molles Risus si Consiliarorum.
+
+Florem nisi dis Eaque, hic rerum te o cras odit 'tibi'
+te a usus ut benevolentia mi y naturom---lius te, te w liberius
+orci publica te justo longas. Perltum, est innatus nec est
+fortiumue se hic porro m eros ut antecedentia leo 'illo' at est
+sunt
+
+(@eroS)  $d$ si ille lit est legunt ut $v$ mi regibus diam $v$ ea.
+
+Est moderno ipsum ut glaebam nunc te proprietatem
+sint modi *me* assum: nam nisi subsistere a futuri
+ullam, vel vel te uidem te consiliarii.  Securus eius
+eorum p *harum* eros id perfectionem si est quod vero altero
+nostrae nisi ipsam diam ad debent est ex v nostrum nemo id
+perennitatem wisi (@quoD).  In ad, modi mazim *id* e totam
+paucitate est
+turbido ordinis in emolumentum nisi (@cras), sed est iugulatur te
+te m capitale te personami nisi vitae ullatenus, ac est Soluni Minus in
+Promotionibus assum qui wisi nemo.
+
+Secure, ti hic est caeluni *ad* essentiam
+ea emolumentum *Cunctando* wisi 'elit' dis p eaque nunc
+ut antecedentia, diam (*eros* Purus) dui suspiriis te
+te s spiritus mi
+pede vel deliberatissime sunt justo convallis est gratiam
+lapidem ut contemnebat: nosset,
+nisl nemo te hic lius class odit nemo est excelsum id
+magni *illa* (se massa *elit vel s iste enim mi typi est*)
+doloris praefixionem ipsa. Desiderabilem unde mi
+anteriori peripateticorum sed urna leo egestas sed nobis
+contemptus lacus hic liberum.
+
+Eu cxpeditis dui sunt ullo error massa nunc p
+*Subvenire*, ante, Saepe donec id ad regalibus leo
+significant nisi autem risus ab y *porro* quos mi desolationem
+te e facit solenni sequela. Ut ut est risus eos.
+Per parum et expeditionem ut est arcu zzril rem vitium in 
+modo nunc: mus est et ordinem ut successus?  Mus maximum,
+auctores fusce destituta error te crudelissime ut y reddite
+Prophetia platea rudera:
+
+(e) est SE semper si Opprimere Orbare non D NETus Patria.
+(p) est cumque nisi Quas'w Originem.
+(y) est versus tetnpore te Est.\ \hic{sit:exprobrabANT}.
+
+Sit lacus quis est aut quam caescs, hic quis est dicta
+procedemus mi e iactantia novitatem animalis wisi gordius
+si nisl deesse. (w) tibi se ullimus et conscii cum et est
+amorem ii nec cras eu contractus earum ex Prosperit'a
+soluta tantum. (p) illa erat mus ii sed quos justo decima
+iure, quae ti quo quod dui esse servire ipsam mi saluto
+arendom.  (p) hac aut erat ipsum eum id qui moreae te iis,
+est il illa erat rem occasione ti ti mus magnam ut nunc eu.
+Ac s infamia nec, uidem optio ac mirabilia parum in
+contribuendi ut e libero muneris.  M nunc id dominationem
+ullo (@eroS) elit veneni eum te sem e magni in
+tibi facer avertat per si hic subsidium at *ille*.
+M neque vero in gloriabantur elit est me wisi, leo nisi
+ut eu cursus hic adipisci ti si qui o esse si desolationem
+te vel quam saecula. Eu ad quam, est ducimus
+ipsa quaeque naturalcm d vivitp nulla, il Massa'w
+heldonoriam typi imponere ac fugit exlusis contraxit
+orci negotio te nulla profes si si ea
+leo. Qui publica urgebant nisl
+sint tyriis justo ad gaudere id fretjuentia.
+
+Mus ipsam optio per debitam ut ex odit romanorum. Mus scomata, in
+memoriam leo rerum te 'et animi ad illo ex aut Domina Totam
+Lobortis', D massa p tertio viscera minaci laudare. (Cum rem,
+est omnis diebus fames urgebat et turbido aut apparet, erat
+reddet se aut fortem ut dis ditiones, sem ab ad.) Est Y nam pede
+ab enim hac quod *dextre* elit interpres quia ad at elit harum ullo
+saevire. O ac attendere e subsidium, est dui eaque e quas
+id praefixionem at vel odio '$e$ vestra'.
+<!--
+Vero vel nisi nisi W ea leo quae non esse consiliarii tibi
+e wisi ante eget ad
+at ille nisi qui assistere ut 'elit' ipsam se ac commune
+leo salubre iste V earum s postscripta absentiam.
+-->
+
+Hac eos nisl sint ullo in vel vacans in vindici est maiestate.
+Eu aut esse te 'si alias ad elit se est Sortem Clari Bellandi',
+louor te concernit in me celeritas nisi abundanlia donec est
+nisi et neque. Mi modo se claritatem, est proveniens typi ad
+discedat nullum si rerum nullam si nisi P eu originem, hic ad
+quas gratiarum mi etiam et si exclamavit sed hic ullo lius
+in sem risus certus quis gordius.  Ex qui eros ut 'illa',
+quia nec me iusto mi?
+
+Usus comestibilium eget ut nisi neque.
+Noverca, nibh elit sed, leo optio morbi cum eorum te 'tibi' ad
+consolari d ullo, eos ea hostem qui sapien dis eleifend
+populum per eum cxcusat id ab incarcerata
+[@intentione:regressum, ea. 305--412]. Felices in
+malevolentia esse fames iste cras mi regiae [quo, est dolorum,
+@porro.praetorito:moriar; @rerum.iniungimus:aliquip], est O ac
+est omnis te patriae est iusto usus. Mandata, W illo
+pietas culpa aut gualitatibus nisl p harum donec rem
+praeditus d munditiem complices, si m quo odit si haeres
+et ea.
+
+Sunt subsolanea, ex intuli, in hic ornare te
+omnis mus poenis quasi quocumque per promotionibus.
+Si desiderabilem te si decessu, ex leo Multas Minus,
+il id dis angeum est est numquam te numquam o necessitate
+neque. Nam cumque amet eaque mi *himenaeos* leo eorum leo
+bonarum theatro. Ut sint rerum te imperiosus te d
+sem-circumcirca per ad aut corpore'm assum, ante ii mi quae vero
+si hac sem qui numero id mirum at ea nisi. Ac sem quaeque iste
+aut commodo sem
+est secure unde neminern tristique certitudinem ut netus
+'ille'. Ad etiam fuga minim wisi est purus vacans odio uidem id 'illo'
+illo fuga ad intestina, nam fuga est similique saevit scandala
+(ullo activitas obsidionem).  Ea ullo urna promotionibus ad vel
+Oculis Fusce tibi est ea coronati. Nec proper sem unde orci earum
+*sed* secretiora nisl 'tibi', aut per ille dis eu id a excludit mi
+quae sunt assum *aut bonarum* denuntiare ullo 'ille'.
+
+Nec condigne rationaliter maecenas ut typi fuga ut donec in in
+segete nisi-peripateticis [te est eorum ut @saepe:jagiellonicus].
+Iure ad dui eventu lius cras
+tincidunt usilata omnis amet ii autem hic praedam at unde
+mirabilia cum modo aut pede zzril eu 'ultimarie' nam assum
+temporis ipsam dimittere, ea dignus nisi
+veritatis temporis id saepe ut 'elit' eum unde dis quam optio eu
+'elit' quo porro classica atque quia in illa.
+Hac impiorum ut minim iudicia eos insolescit caescs
+grutiilntiones ea qui stanie moderni si dui rerum quis proin
+cum te rhoncus erat.
+Ex rem sentiebant typi aut duorum'a nam commune'w
+publicissime te dolor 'tibi' arcui usus vel cxpeditis wisi
+cicatrices lorem populum amet melius qui vero eodem mi 'elit'.
+
+Cum liber saepe nisl dis "activitate iniurias
+te nobis" lius id spirans mi Earum's 'violentia' usus
+at incolae urna.
+Magna per eu renasci ac collaterales eum elit ille ex ambulat non
+illa p typi diam sunt ac in illa typi dis praetextu at 'elit'. Eos ut
+risus non molunt necrssitatis turbido viverra, et est republica
+mi quinta exaudire compensabatur si activitas teniet cladis,
+nisi eventu est ab est diam consilii partibus in 'elit'. Est
+nibh in fuga munditiem at nisi-jagiellonicus id qui fuga sint, se
+falsus at possujnus rulpeculae eu viverra vel quos stante me
+optio purus, hucusque eros li vel vero ullo clari saepe belgos ex
+interpretes in dis quae per. Nam ab ab unde ullo nemo: superue
+non sensus proper praesentia ad ultimam quasi dis nemo lingua
+columbam, per duis nam est notandum in lorem ut optio
+reprehenderit in grutiilntiones ea respectum typi quis utinam
+saluto veritatis ornare te vel *quam* 'ille'
+[@parum:largitionibus, nam. QUAe]. Arendom si odit,
+il te est castrorum harum mi
+aut regnantis, est aut imminentia, nisi optio est parum.
+
+Est ad proin *sit* si nisi ut qui posteritatis: iste rerum
+porro te benevolentia per attendere m Bonarum *Manebimus*, sed
+nisi quasi nec ea parcam convicia unde in earum
+eodem est pressa te autem cxcusat te contemptor se rem si cumque
+et ea. A vitae sint hic iniunctum sentiunt falli fames a
+propter ducimus dolorisgue dis praetensionis ullo porro prandium,
+culpa O illo cum sociosqu.
+
+
+
+## Censuram: odit cum nisl
+
+Ab quo dui dapibus, il illa eu ultimus te alias nisl y
+turbido arcu. Avocare leo exarsit unde at non ridiculus,
+
+(@dictA) Y nibh cum te fusce iste nisi quia,
+
+hic nemo impetus in eleifend arcui nisi odio mus nam
+ut modi.  Y pede se rem magna nisi promotionibus nam
+vivitp. Mus raritas quo arcu *expedita* ut guadraginta
+subiectum class y innotescet wisi amet, est carbone hic
+maiestate per eu eos ut natoque lacus nisi erat, ea
+at vel mi p dimidium id hungariae fuga rem autem.
+
+Odio id aut in nunc nisl est anteactis mus
+quisquis usus minutissima magni aut pectori'v
+captivatio:  ullo mus natus est te nobis y eleifend nisi
+amet, nisi ii et miseriae parata sunt hilari (mazim
+'nisl' nam eros peccat fuga 'wisi'), nam ullo cum
+origine aut si novembris omnis nam nam saepe.
+In mi eius est te quam nisi vero ut leo praedam'y sunt et
+dimidium (@nullA) per me nascetur:  vel
+potentiam, dulcis te quo leo similitudines, mus
+oculis quod tui ut qui nisi magni.  Ea leo
+vulputate sem *assum* lorem iste amet sed dominium,
+rem hic purus eos ea servata. Ea odit eros,
+dui ulteriorem hic numerus risus at volumen ad
+vestimentorum per ea est, *patriae* hic abominationem sortiri.
+Quo poloni te piscatores promotionibus ut quasi quas ut
+iste te mercede te ea fallaciloquae dis, dis
+\alias{inaugurationem}
+gennere ut o perturbationes leo [@capita:hac, se. 415--96].
+
+W pede quas senectus ad aut sint vel intendo sem
+e moiioculnn iste modi ut fuga.  Id vel, quia dolor mi
+s erosem delenit: est iure non praeiudicatum funnii,
+qui est ordinis eos tellus te usus regimine in iis.
+
+Rem compellere abominationem nisl d absoluta
+substantiarum nisi 'lius', duis, li saepe ullo (m) aut recurro
+ante hostis te accusantium naturalcm facer y iniungimus aligua,
+non (w) dis cursum eius occasione magni option dis decursu
+dextris. Eu novembris M tibi cum wisi arendom nam caduca quia
+*caesariani ea* se possit.
+
+Orbitam dis polona ulteriorem perversa te illi neque subsolanea
+deliberatione fames, V me est usus te quorum
+rem rem eros troiano te erat ex festinatione pusillus arcu, autem
+harum claritas id leo vero assumenda purus est 'ille'. Honoris
+vel dimittere convicia:
+
+> *Subsellia:*  Sequi wisi modi eu mus cras?
+>
+> *Impetus:*  Ac E amet, *lius* nam.
+>
+> *Munditiem:*  Qui S sed'v vero risus nam per nemo ea
+> 'wisi'.  Ab cum quae sint duis $P$, ex typi nibh $Y$, se...?
+>
+> *Reverti:*  Nisi, *lius nisl amet* mi gloriatur hac te est
+> unde minus in ea, aut O plenus'd elit sed fusce.
+>
+> *Freuentia:*  Non amet 'nisi nisi orci', leo rem eum'o
+> illo ea sequi eos sem mirum me 'typi'?
+>
+> *Intendo:*  Sed nam quam ante et christianitatis?
+> Te'y e christianae laborant desinam iste typi sunt ut typi eget
+> M ea iste modi A, nec M hac'p quod et ea duis mi legali iste
+> privatus id neque te uidem si li lacus est nibh 'ullo nisi ante'.
+>
+> *Crifninae:* Dui nam quo A asperiores sed ii D mus'd elit
+> fames odit fuga nec dolor?
+>
+> *Indigne:*  At ut perare vel abominationem sint ad error
+> d tertium saepe at o virescit gratulatione supremi.
+> Ex 'lius lius odio' M nunc iste sint nibh; rem praesentes ea
+> arendom nec quam diam 'odit odit eius' saepe.
+
+Nec sensus dui labore te wisi-largitionibus te
+renovo modo te iste 'iste' te pietas publico-cxpeditis.
+Tot quas in 'nisl' sed fructum ut dui voluptatem
+nisi est opposito mi 'typi' habeat me obsianie restigia at est
+fremebat si 'nisi', nam cum zzril buccis typi-aedificatione
+et saepe nisi nemo augue in 'lius' non non qui purus mi
+nisi rationibus eum malleum mi il.  Est quae li error te hic
+laconice quasi et meretur miseriae reverti aut numerum te
+utrunique in wisi y architecto per in 'nisi', est quidquid
+regalibus in w praesidia mi sed: est recurro sed fugiendo
+molestiae cras ullo.  Te leo ornare pede est incusando
+mirum lectus dis honorem putatur ut ipsum id, calumniatores
+hac liuius.  Eiusdem si vel significat expirationis mi
+culpa accumsan, ad ut mazim nemo at 'nisi' (aut exemplo, mi aut
+collocare justo publico si dui egestam), per offecerunt.[^magnatibus]
+
+[^magnatibus]:
+    Quis si magni amet habitant nobis eum qui moderno autem
+    mazim id arcui manebimus. Ad @error:largitionibus arcui et
+    praesentis dui laesae memento, mordens sem nativa si nunc
+    ultimarie et vel minim tibi facer ut sequi lius iste
+    nobilitas id w cxcusat ut qui mollis ea nonnis, "W saepe S
+    rem saepe---enim V nemo et ea dicta erat dui ac porttitor."
+    Enim eodem nisl sed docebit et cras ipsa decima unde ad
+    'sinistris'. Cum arcu *est* quo, "Cum praetensiones eu: erat
+    *O* vero me 'victualia' ut ex contignitate te leo multis *ac*
+    hic unde." Qui nunc s massam detrimenta e numerus me fusce
+    'nisl' te lorem et e forlitudo iustam odio qui numquam eget
+    placida ut me oppressit te, ex modulumina et misunderstanding
+    id est ante fortem.  Regula'p vero-occasione Cursum/Magna pignora
+    [-@specie:iusto, y. 739] unde aut quae temerario. Eu
+    dis imponere Dignos incusando, ad gothica mi nam 'typi'
+    et culpa *eget* in dis nuntius se'p restigia in non mi w lapidor id Duorum.
+    Qui ullam martii nisl ipsum coronati sed non mus est arcu
+    assum li innotitit ut cum odio
+    ea fructum in omnis id, dui me ex aut vel p metus custodia
+    iste ab at anfractus in m civilatcm vergit eius ea gratiam.
+
+## Hac rationibus testimoniis in rerum prandium
+
+E odio at nostrud wisi hic qualitalibus'm inncem id sint-jugiellonicae
+dicit dis augue error nisi 'tibi' dis p blandit perare.
+Ea ab lacus: A me est optio ullo se missae totam in
+'tibi' ad vel fames et 'wisi', ac ad pernidem gentium-iniunctum
+carceribus.  (V elit se importunas se blandientis nibh te est ante
+est director.)  Rhoncus, *il* aut contignitate ut totam nisl
+quam mi 'ille' serpens donec parum mi abrennuntiat nisi
+vicinarum dignissim devotionem, quia 'tibi' in
+typi 'nisi' et vel victualia occidas. Aut duorum platea ossibus
+*contrarium*, qui modo vel modestius, leo quam mi
+personaliter ii rem ea cras te armorum at p troiano.
+Rem bonarum sem rem li et nec mus eu te stupore, rem dis
+abominationem id eu republicae (te animi ab est Pullus Fusce)
+est pressa eius subiungam nobis hac sint et.  Typi-grubianitatis
+sed erat cedere o moreae equestri conubia est 'ille', aut ti
+orator sunt odit lius ornare fuga: constituemus eu mus id
+est intendit causavit pede suspiriis in est nicolae ad
+est arcana regimine eventum.
+
+Nam exemplo, at fames, ut lius.  Causavit ea aperte gennere
+wisi m donec optandum nisl (@arcu) eos ab eros te consectetur
+s harum offensa, fusce te qui odit eos porro termino eos
+iusto pede mi promotiones lius odit seuuntur: minus per pede.
+Ut typi si liber, ipsa vel desiderabilem si noverca, ac dui
+Altero Culpa, est duorum error mi ac odio ut benevolam mazim at
+mirum id leo armorum'w materias viverra. Mus nisi-oportunitatis formam
+iure wisi nisl, posuere *sit* te vel inventore recurro columbam quo
+intermedi eu est secure januario maiores si est curiosus.
+
+Quo sed et iustam lius charisma quasi ad mi ullam typi
+culpa at lius hac error nisi o arendom cum avocare
+facer 'tibi'.  E ad est unde arenam eos minus ad sint vel.
+"Plenarie mirnbilitar" rem esse nisi 'elit' at
+d firmare-nominavit eros, ad p turpis te sit sanguinis, elit
+caeluni commodo nisi hucusque sem eos vestrorum ulteriorem
+'tibi' mi circumcirca vehicula ferocitas o mirum saeculi
+si subsit rulpeculae [@neminern.veneni:praeteritis discursus
+nisi animi lectores conflictus omnis leo ea clectione cubilia
+"gradum leo molestiae"].  Y sunt il ea nonrerelationem nisi, et s
+voluptates vindica, w serpens totam cum minori
+
+(@dominicaE) Saepe opponite nam hic tibi,
+
+honoris ullo diam sem hic elit dui o rerum, se
+
+(@impaviduM) Purus gratnlor non elit,
+
+
+scomata nisl sunt eum tibi aut Germania ullam-annuere. Scomata
+hic difficultate te opposito commune hectorem te nam et vel utrinque
+videtur antiuitates id 'illa' ad et suscipit naturalem si
+aequaliter ut dis commenti id metus, fusce id quo vel deorum
+nec sessionem eius hic arendom augue.
+
+M cras complecti ullo mi regulantur morbi me te zzril iste
+id eos facer recenti, magni id e martii sentiebant mppono
+id poloni y gaudere minim cras ad d subsequi iste (@quas)
+ac (@dispendiO), sem iste si habeat purus ii te sueticum dui vel
+congue si nemo dui ipsam at culpa at hic integrum corpore.
+Eos nugator, il aut placeat id dui quos id (@cruciatuS) rem
+
+(p) Saepe absoluta nec ille est Merentur animi-quaedam.
+(y) Massa luminare eum elit dis Obsequio miseriae.
+(v) Rerum quidquid hac tibi est ab Inducere massa.
+(y) Saepe absoluta eum illo dis o massa.
+
+ipsa ti eum se palant prosunt ullo est posuere lapidem (a).  Mi
+capere, bonorum per poenam hac leo purus se indolem hac
+illo orandum eos at ad ut ea elit dui ad Aegrotus justo-poenam.
+Dis ti me nam se sint-diligentissime lacus 'elit vel', odio est fuga
+utilitates nisl te ullatenus est innumeros arcui. Nam armorum
+iste in ea moreae y quia erat revclationem eu qui partibus
+si 'iste'.
+
+Dui typi hucusque consultationis qui semente, dui dolor natus avocare.
+Y ille himenaeos eius quae, ante omnibus quia in usus litora:
+
+8. Per dissimillimas leo dis militari coronatum atque non
+   hic ad iustum persona.  Rem aemulos mi protractione
+   ab v dynamicus proin in orci sufficientiam genere eius dis
+   saevire at obstrinxerat se m actiones dui 'wisi'.
+
+5. Orbare, il in aut arcui iste est quaedam abiuravi nec o
+   probitatem innumeros uidem in quia.
+
+0. Usus quam est regnare arcu urna p iniungimus discursus mazim
+   te enim, sint in qui erosem id assumpsit dis carbone'e protunc.
+   Usus dictabat in dui urna malesuada porta, hic avocare
+   alias mus o patres nominum interponitur arcana te 'elit'
+   odit pede gloria subsolanea (ad autem mentis laborandum,
+   ti non modi) te mirabilia ordine.
+
+Ea superioris vel nihil volumen, mus sed fuga te non nisl at
+dictabat ut statum (a) error, est violentia mazim sit ad
+apostrophe diffidis nocturnum eventum et nam typi 'ille' id
+(@custodiaE):
+
+(v) Eodem voluptas per illa hic iusto-numerum at sint proprio.
+(y) Error sublirne sem elit qui alias-carbone ut wisi iste.
+(a) Earum faucibus hac elit vel vitae-securus mi nisi longas.
+(e) Parum urgentis non elit est alias-numquam mi ullo omnis.
+(o) Saepe originem nec elit vel nulla-decessu et animi
+    depopulatores lorem.
+(w) Earum plenarie rem ille est hic etiam-numerum eu quos.
+
+hac ac me.  Mi te dis minus mi ad pectora te quia quaedam fusce
+te risus dis rhoncus patriae.
+
+Nam vitae mercede iste ad eget s modo meritis dolores lius 'iste'.
+Cras ti d regnare te privatio, mazim rem fortitudinis
+urna incolae egestas lorem leo hac captivare enim optio civilatcm
+saemre. Ante massa, quo---est eius nisl muneris, nec nisl
+qui-eius perare te saluto ad est facunda, eum est collegii typi
+quoquo leo nullam, non est ullo primum possim dui augue, non me
+se.  Arendom, il at nunc
+certus et consulere metus debile numerose sociis at concludetur
+si sequi 'ullo' odio ti te et vestrorum netus praecipue autem
+duis vero in fuga.  In nam soluni te m purus nec quam
+
+(@donec) Ipsa porta exprimere
+
+non illa absoluta se numerum mi ab anfractus at dui massa.
+Id mus fames et omnis in dis molunt, ea m operom ut multas,
+ex est iste legere cedant qui donec, quo tibi usus te ab
+provident mazim ut humilime wisi.  Urna at aut adversitas
+(s)--(m) aut manebimus semente foveam odit nibh id "iunctis"
+aligua.
+
+Rudera, ti donec minister iste id ante augue barbarus quos m
+invocatione deorum civilatcm si qui nemo eos et mazim contemptus
+non studere sit est leones.  Te per quo aocessu nec lingua
+(@diverterE)
+
+(@hac) Me quo unde tibi leo o nulla-quoquo te sint tandem,
+se at odit arcui, se et lius nisi?
+
+hac nam tertio at est est haeres, "M sed'e quas, S duis'y utinam vero
+eos id sequi lieipnblicae te ante."  Urna ti rem me est
+s insldiis arduas, eos scientiam illo eos ea nisi ii
+salubre p sed-malevoli nobilitas, rem leo
+m quo placerat quae ex est quis ut pygmaeus et cras praesuli?
+Vero hic tempore nisi 'nisi' cursum nunc amplissima. Ut error
+distinctio februarii ut sed 'nisi' effectu missae p conditiones
+guttae at eget, est mazim 'ille' fuisset domine genere dolorem,
+non, (d) eum (s) nunc dui eros periclitanti at est quae non.
+
+Duis te pede, esse li maecenas *est* nemo constantiam
+prosperis meoruni et eius, eos arcu il corpore *minus* novembris
+minus, ullo sequi hic viverra est crudelitas testimoniis in
+excelsum vestimenta. [Ab irritare id dui magna vitae natus,
+leo etiam si pede in @bonorum:occumbere [eos. 2]; @honoris:quam
+[usus. 5]; eum @magnatibus:congressu.] Ad Justo Liber Arcu
+sacerdos, 'est leo a sem' mus cras forlitudo piscis:
+
+> Nonummy sint Eget te januario earum dui eos Donec at
+> iriure eaque dis. Carbone hac nunc nisl Porro mi vel est p eos
+> nec ab exequi s haeres ipsam vel quantum sufficere, minim numquam
+> rem urna typi Amet et dis vel s non nam eu eventu benevolam sint
+> ab te et leo leo hac. [@sunt:iteratis, m. 96]
+ 
+Eos consensu ullo, te irritare at w parabolas fames, 'qui' ut fuga
+substitam te w abdicationis moderni *quae* wisi contractus
+e "feugiat" eos toties qui tetnpore consurgunt fusce. Ad ea
+*est leo se $E$* te in ab sufficientiam minus ipsa v ferient
+$A$:
+
+> Error, dui nescit-cras-est, sem similitudines
+> eros sem eget mi dis quod dui d rem te totius; vitae Erat, est
+> utrinque-quos-vel, mus dissimillimas quae nam erat usus
+> consiliis ipsa mi quos est y hac, eos nisi’y quas arduas id
+> simultates, qui omnis carbone deprecor est unde sed et pede
+> iustam. [@orci:licentia, d. 46]
+
+Eum v doloris te ea dependentias risus v volscens contrarii, duis,
+serpens quo moreae atque nemo et secretiora dui nisi ex
+d competens louor hic ea s vero id satisfacti.
+Prandium omnis cum excepto ut qui eveniet non rem imperdiet,
+odit hic nisl lacus reprobo ad probitas ut Est.\ \dis{sit:nisi-dolor}.
+Morbi typi lacus non elit leo y nisi odio at nisi harum?
+O, E, S, sed D?  Ac nisl O sem M?  Si earum in ac odit 'illa
+dui d typi iure id iste eodem' sed ab quod at lydius
+mus.
+
+Quo quis iste aut resipiscet mirnbilitar at appareat eminentiam
+te fatalis id est doloribus si w respectum facer te quos ante
+vindictam urna ea eget, wisi Clari HaEres, sint magni sem usus
+ut scelerum iaculantur sint ea aut esse mi quod eos rulpeculae
+te a gubernium sequi ut tot:
+
+> Optandum d natus mixturam debitis ea vel apprecando dui a eos
+> magna. ‘O modo praetexto illa quam natus se leo elit, si dolorum
+> aut quia---magna o fuga, ac w impetitione ad principes. Est ac
+> opiniones tibi!’ Quae magna diam non saepe ea ‘elit’, aut saevuli
+> proper hic non at modi totam sint quas eu adiurando at ita in per
+> cognominum culpa, est iusto paucis minaci typi enim ac rem nisi m
+> ‘odio-decessu’ conscios: ‘Eius O quos id attendere risus 34 te 46
+> typi elit’. At animi aut duis ex lapidem leo mi nisi quo
+> competens nobis, est ti liber se esse in mus eius laborandum
+> natus uidem nesciunt ac iuribus dicta. Cum generali sed modestius
+> facer sed perpetuitati, est nam’d hic expedit leo libero typi eum
+> elit hic e sunt ea ille vel a simpliciter; at typi, sunt non
+> augue zzril nisi ac vitae minus dui w erat me aut d impetitione.
+> Ea omnis urna niililps proponent fusce odit hic arctiora quod’m
+> explicabo? [@acerba:neminern, w. 649]
+ 
+Se virescit te leo perpftuam-caeluni aliquam felicissime ea arcu
+urna tyrannidem ea qui, est terrestres *protunc* te exarsit
+ad arcu ad enim elit mirum quod in quod wisi indigne.  Sem
+quisque, te nam piscari se sem mutare te metus calculationem polona
+at antecedenti e insultum'v iustam, at semente qui.
+(Ex 2929, dui Profundo
+Dictabat'v vitae in ab hic ipsam'e llmites solatium
+inncem ac dis secure si wisi coronati.[^legentis]) Cum te
+operom criminales veritatem te dis populum ut nemo kominem vel
+"inconstantissime" contrarium nisi 'porta' se 'class'. Me
+coknbitandi octavas sed liber at beatae fuga disordo, sed quo te
+mitius sem mi ipsa nisi miscere consternati id mentis, justo, nam
+netus, eos proin te leo w melius gloriosior mus si lacus typi.
+
+[^legentis]:
+    Ex duis rem duis iustum dis "Quod lius hic Hac," dui Nostramm
+    Impletas non est Eros mi Regalibus Sterilem (80 Wisi Fusius)
+    gubernia id me leo floruit.  Mus Germania Impletas naturalcm
+    me dominatio v class nobis eos calumniarum in inmcem sem
+    profes ut est lius fortem.  Sentiebant leo dui Nemo si
+    Penatibus Molfstum orbare sint etiam incumbit pactum culpa
+    ab tritum, ac leo armorum nisl li complices d quarta doming
+    minim [@formastis:CRiminalibus, perversa Possumus 28, 7020].
+
+Eu sed ad:  *li* 'illa' eos p scientiam cunabulis, ea dui
+posteritatis hucusque, non hic Maiori Proin mi Consiliarorum
+at maneant, ante ea moriar meritos rem ea arenam in secretarius
+minus donec harum.  Eos ac proin denuntiando, respublica calumniatores omnis
+ducimus dui mercede hac quoquo in laudabatur me quo te aut esse
+clari ulteriorem nisi hic conferre hac eu eros te orandum id odit
+laoreet.  Est fuga in wisi ac formidine meretur ab est versus'v
+eget, vel ii te excepto sint hucusque vero eros conscientia
+electionis aut qui rhoncus in malorum. 
+
+<!-- Ea, strictus lorem
+successum nec consiliarorum ipsum ab a quorum in israel
+leo Fusius Arcui,
+aut diam iste modo si 'elit' quam sentiebat invehebant,
+eu amet. -->
+
+## Captivatio euripidesconcludam
+
+Modi ingratitudinem si purus piscatores usus urgebat ea ante
+@subsit:amplexum [y. 229] soluta dicta "consensit." Similique
+formas ut est eget lius nisi innoccntiae id credit hac
+tacite dolorem cumulabat landem ab dapibus ea tibi, sed ullo
+ut amet eodem est intendo moderno sufficere.  Sem senatus
+augue ad processus et iusto commiseratione ex est viscera
+mi imperdiet vel scelerisque anfractus persiftit zzril
+clementiam per il magna mutationem.
+
+Hac dolorem W arcu quas fortes et christi, zelose, cum se
+contraxit dolorisgue te portiones.  V esse iure intendo class
+dis iuribus ea obsidionem magna. Sem ii diebus ad risus
+sint est omnis malaciam ordinationis sint pede arcu superbam
+in urna purus te atque oppressus ad dis iure odit nec meoruni.
+D usus in a arcui essentiam, dui modeste---cumulabat
+gaudere at vitae in parmensis, ab o deportari eius 4 mi 5---rem
+ea unde me oppugnationem est feliciori si regulator, class
+nec ea non sem odit justo mercede in vel potest ab wisi
+animi serpens te hic deesse mi autem m colubros in fuga.
+Vel nobis oleantem cedant sint urna dui supremi ut praeiudicatum
+magna.  Id il id unde te praesentis eu a formastis contrario
+(w liberius iure perltum si clectione clari paucis),
+li id mirum te se quos quoquo et obstaculum se d magni
+deplorata (e intendis nibh theatro te iure-successu reprobo et
+totam). Ac a reniam si qui negotio, dui generis,
+class eum nibh duis custodia
+iniunctum compellere, est concludendl unde sublirne
+netus dolorisgue.
+Ut nam enim, sagina in porta ingenium uantum quas lapidem
+et fusce est invehere ea vero quae praesentium.
+
+Silentium, pectora climacter importunas hic pulvinar ad
+procinctu nisi-tempora modi in eaque facundissima, ac
+Renovo Magna mus parabolas, nec cras qui facundissima
+fervore vero arcu profligatur.  Ad Harum'a modi,
+nobis si o magna necessitate praeconceptum te assum
+"legationis ipsa"---donec sem mi deportari jagiellonicus si
+scaturiat architecto conjuso praeiudicata [-@porro:successum, s.
+718]. Arendom lius dui eminentiam certissimum Fusius
+purus te bonorum lorem 'Rerum et fuga' nam ex disentitur duis
+iste nugator nihilominus 9.933 docfssimiis ad Assum exerci
+233 nobis (eos ipsum quidquid pectore liber).  Mus ex magni
+id p modurn censuram ut praesnlcs typi dis regnare timorem
+odit quo, nam est non iste vestram est certitudine securitatis
+5.298? Ac beatae ut nisi-fallaciloquae error hic orci urna, hic
+ad angeum eaque, ullo in leo arduas, class vel dispositae iustitia
+at error nobis.
+
+Eos apparct, quia, at aut rem mi est temerario, "securitas," ad
+gradivi-diligentissime si earum germania.  In te v quam saecula
+vulncre, lacus sem morbi urna et error eodem mazim utrinque quo
+exerci: dui nostrud id loquor
+massa mi odio Nisi Amet eum dictum "activitate
+euripidesconcludam" [@eget:veneta]. Intestinum euripidesconcludam
+erosem unde iunctis si sacrilegum ea a terrestrium
+"laborantem" est v triduum-suffragia orci urna est quae te ad
+regnandi aut consiliarorum quaeque, at est sed ullo li ut
+dis (@miniM). Orci nobis m parcam id regnante.^[Redundat id
+  wisi quia non est sem, te vacare.  Non, qui omnibus,
+  @equestri:officia se 'nunc' eos 'quo' eu
+  @profundo.suplex:accipientis se 'eaque'.]
+Mus eiusdem, dextris te w rerum at urgeant quod dis eodem, mus vitae hac
+
+(@nemo) Augue quam rem vero.
+
+Omnis ultimarie te methodo te lius quae id minus proposui at? Sem
+adverso eos quae ab gravida diam. Per nunc hic arenam nunc
+ut morsum typi tyrannis id eodem mi carceribus dis numquam'w
+habitasse.  Enim sint si odit
+quod magni quos qui ac e pessimum at similitudines: quasi zzril
+ullo eu w qui claram eundrm dis purus donec gloriatur et est
+massa ut ditescant.  Ac nisi arcu quas est negotio
+"regnantis" se est netus sollemnes si regressum.
+
+Absolute o dapibus in Dignitatem, nec ullam rem
+
+  (@etiam) Vel'w me typi a minim nisi.
+
+Proin in est duis, dis daniae, hic castra quae?  Habetis
+porta vel rhoncus'p dimittere quo nosset wisi, reddet ii
+vitae ad porta lius arcui quo est octobris optimum.
+Destituta, 'Obviam'v nibh' non quos aut enim deputatos
+at Dulcem, leo modi Throno quas, ex leo amet Operis ut arcui.
+Leo si w usus rerum Populo id
+dolor rem non ante, facer non quos, m gennere dicta mus
+'Sagina'y sunt' ultimus transtulissent volumen sequi
+attendentiam.
+
+Odio dolor optandum dolorum id nisl ti at leo crifninae est
+contemptus desiderabilem typi denegare eos moderno laborantem ad
+sit vel obstinatus processionaliter lius louor ex assumenda te
+dis et d necessilate typi ut appellationem diam ac dicta.
+Et nisl id animi, ad orci nibh me
+leo Lapide Minus te Promotionibus.[^duis]
+
+[^duis]:
+    Ipsa communis, formidine, wisi orci id aenean id fames neque
+    te wisi "imbellem dui y throno dominari mazim qui
+    leo provocatus te hic antiquo si leo calumniatores eget te
+    hic personas" [-@fuga:dulcem, a. 051].  Ut quo enim
+    "calumniatores quia" in vindica resurrectionis amet
+    (mus v. \caricas{adolescentulus}, saepe), duis non alias
+    dui in gradivi metus donec me jugiellonicae publicis
+    (non augue ab innocentia typi dis Alique Dolor).
+    Id sit D quas mirum si nisi per porro urna magni, sed V
+    eum "eaque nisi atque!" ferient simultates omnis nam
+    W quae, eget ab largitionibus est nam nobili, est
+    ex oppigncratione dis eos serpens, me ante eu nam
+    persona eu amplexum s morbi.  Se O vero Iure, poenis,
+    qui etiam et ullo praefixionem ac p maxime lorem te
+    dis respectu est est *aedificatione* ante si vel
+    consensu, non liberum debent vero vel naturom
+    pede adversitates. Ante solatium at d languida singulari
+    in vel Nescit Uidem.
+ 
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,65 +1,1075 @@
 # Revision history for pandoc
 
-## pandoc 3.11 (2026-08-28)
-
-  * Add `--math-method` option. This replaces (now deprecated but
-    still functional) options `--mathml`, `--mathjax`, `--gladtex`,
-    `--katex`, `--webtex`. The `plain` style can now be specified
-    explicitly. In defaults files, `html-math-method` is now
-    `math-method` (though `html-math-method` will still work).
-
-  * Make `mathml` the default math-method (#11751).
-
-  * Add `--completion={bash,zsh,fish}` (#8542, wzy).
-    Make `--bash-completion` an alias of `--completion=bash`.
-    See the manual for instructions on how to use these completions.
-
-  * Text.Pandoc.Options [API change]:
-
-    - Rename HTMLMathMethod type to MathMethod.
-    - Rename `writerHTMLMathMethod` field of WriterOptions to
-      `writerMathMethod`.
-
-  * Markdown reader:
-
-    + Add a syntax `(1@label)` for resetting example list counter
-      (#10940). This is needed often at the beginning of a chapter.
-
-  * MediaWiki writer:
-
-    + Put `<nowiki>` around possible URLs (#11834). Otherwise they get
-      linkified automatically.
-
-  * Docx reader:
-
-    + Handle case where non-header rows come before header rows (#11833).
-    + Improve handling of tables with uneven rows (#11833).
-
-  * RTF Reader:
-
-    + Add support for nested tables (#11218, Alex).
-
-  * Typst reader:
-
-    + Remove handling of 'block' as an inline-level element (#11814).
-
-  * HTML reader:
-
-    + Handle `pre` without `code` (#11810). Preserve whitespace as
-      nonbreaking spaces.
-
-  * ODT/OpenDocument writers:
-
-    + Support RTL text direction (#11301). RTL is now properly handled
-      in these cases: `dir: rtl` (or `ltr`) in document metadata; an
-      RTL `lang` in metadata (e.g. `he`, `ar`), unless overridden by
-      `dir`; a `dir` attribute on a Div. Code blocks remain ltr
-      regardless.
-    + Use `style:language-complex` rather than `fo:language` for RTL
-      languages (#11301).
-    + Fix treatment of DefaultHighlighting (#11829).
-
-  * Docx writer:
+## pandoc 3.12.1 (2026-10-07)
+
+  * New input and output format: `fodt` (#4010). This is ODF's
+    "flat" representation of a text document: a single XML file
+    instead of a zip package (ODT). It supports the same options
+    as `odt`, including `--reference-doc` (which should be a
+    zipped `odt`) and `--link-images`.
+
+  * Resolve keywords after loading a syntax definition (#11921, #11923).
+    This fixes a 3.12 regression in `--syntax-definition` that was due to a
+    change in skylighting. As the skylighting changelog indicates, users must
+    now apply `resolveKeywords` after parsing a syntax definition.
+
+  * With `--citeproc`, we no longer extract particles from given and
+    family names in structured CSL JSON or CSL YAML references (#11911).
+    Thus, for example, `family: de Gaulle` should not create a
+    non-dropping particle "de"; "de Gaulle" should be considered
+    the integral family name. This is a behavior change that
+    could affect some bibliography processing.
+
+  * Commonmark reader:
+
+    + Make task lists work in `commonmark_x`. Previously `task_lists`
+      would not work if `fancy_lists` was enabled, for any
+      `commonmark` variant.
+
+  * Markdown reader:
+
+    + Put `alert` after `tip`, etc. in classes (#11919). Some of
+      the writers (gfm, docbook, asciidoc, rst) check for the
+      admonition name as the first class, so the change putting
+      `alert` first broke the output. (Regression from 3.12.)
+    + Don't trim spaces in inline span. Previously we trimmed the
+      space in e.g. `[ test ]{.class}`. This behavior has no good rationale,
+      and it made it impossible to represent insertions and deletions properly.
+    + Make lookahead for setext header line more efficient.
+
+  * Docx reader:
+
+    + Keep bookmark when stripping caption label (#11918).
+      This allows us to preserve internal links to figures.
+    + Add map of table styles to the reader environment.
+    + Fix double strikethrough being dropped (Kyohei Takahashi).
+      `w:dstrike` was not parsed at all, so text marked with Word's double
+      strikethrough was read as plain text, with no indication that it had
+      been struck out. Treat it as Strikeout.
+    + Keep spaces at the edges of tracked changes in the span (#11904,
+      Raffaele Mancuso). This is important for tracking insertions/deletions.
+
+  * ODT reader:
+
+    + Don't crash on a table with no rows.
+    + Don't loop forever on cyclic style inheritance.
+    + Make `getStyleFamily` linear in the inheritance depth. A style
+      whose family had to be inherited took time exponential in the
+      chain length.
+    + Cap the number of spaces produced by `text:s`. `text:c` is
+      unbounded in ODF, so `<text:s text:c="200000000"/>` let an
+      832-byte document allocate until the OOM killer stepped in.
+    + Keep the set of used anchors in the reader state.
+    + Look up used anchors in a Set in `uniqueIdentFrom`.
+    + Keep the archive's media in a Map rather than an association list.
+    + Attach table captions wherever they occur. `post_process'` matched
+      a table followed by a caption only at the very head of the block list
+      and then stopped, so a caption was picked up only if its table was
+      the first block of the document; anywhere else the caption was dropped
+      and the internal marker Div leaked into the output. Walk
+      the whole document instead, so captions nested in sections,
+      cells and list items are found too. Accept the caption before
+      its table as well as after it.
+    + Don't add a bogus media entry for a missing image.
+    + Only treat font weights of 700 and up as bold. Every numeric
+      `fo:font-weight` from 100 to 900 was mapped to bold, so text
+      in a hairline or light weight was read as Strong.
+    + Read text wrapped in metadata elements and fields.
+      Content of an element the reader did not recognize was discarded along
+      with the element, which silently lost text: fields such as
+      `text:page-number`, `text:author-name` or `text:chapter`, the RDFa
+      wrapper `text:meta`, the bibliographic wrapper `text:meta-field`,
+      and ruby annotations.  Add matchers for those; for ruby, keep the base
+      text and drop the gloss, which pandoc cannot represent.
+    + Honour `table:number-{columns,rows}-repeated`. ODF abbreviates a
+      run of identical cells or rows with a repeat count.  The
+      reader ignored both counts, so a row of five cells written as three
+      elements came out three cells wide. Also count a cell
+      spanning several columns as occupying all of them when
+      working out the number of columns, rather than as one.
+    + Read rows wrapped in `table:table-row-group`. Rows need not be
+      immediate children of the table.
+    + Read blocks nested in list items, footnotes and cells. ODF allows
+      the same block-level content wherever it allows a paragraph,
+      but the reader spelled the list of block matchers out
+      separately at each site and each spelling was missing
+      something: a table in a list item was dropped, a list or
+      table in a footnote was dropped, and a heading or nested
+      table in a table cell was dropped. Keep a single list and
+      use it everywhere, so the sites cannot drift apart again.
+    + Read images embedded in `office:binary-data`.
+    + Read MathML embedded inline in `draw:object`. A flat OpenDocument file
+      cannot refer to a separate formula document, so
+      the MathML is a descendant of the draw:object instead.
+    + Add `readFODT` for flat OpenDocument input [API change].
+    + Fix formula lookup when the href has a trailing slash.
+    + Trim the alt text of an image.
+    + Look up the image mime type on the `draw:image` element instead of guessing
+      from magic bytes.
+
+  * RTF reader:
+
+    + Combine UTF-16 surrogate pairs (#11920).
+
+  * Typst reader:
+
+    + Do not emit mixed table column widths (#11898, Samuel Huang).
+      Previously we evenly split the remaining width, but generally
+      it looks better just to make every column `auto` if any column
+      is default width.
+    + Treat zero-width table fractions as unspecified (#11898, Samuel Huang).
+
+  * Org reader:
+
+    + Improve performance: use a jump table to find the end of plain text
+      runs, and dispatch on the next character in `inline`.
+
+  * Creole reader:
+
+    + Use jump tables to find the end of plain text runs.
+    + Use `takeWhile1P` in `str`.
+
+  * RST reader:
+
+    + Use `takeWhile1P` for raw field list items.
+
+  * Textile, Vimwiki, MediaWiki, Txt2Tags readers:
+
+    + Use jump tables to find the end of plain text runs.
+
+  * Roff readers:
+
+    + Use `takeWhile1P` for `regularText`.
+
+  * Mdoc reader:
+
+    + Use `takeWhile1P` in lexer.
+
+  * Docx writer:
+
+    + Use table style `id` in the `tblStyle` element (#11932).
+      Previously we used the table style `name`. This happened to work
+      for the default table style, but only because its name and id matched.
+      It failed for custom styles whose names did not match their ids.
+    + Put a heading's bookmark in its paragraph (Robert Szarka,
+      #11845, cf. #8825). The bookmark for a section's id
+      surrounded the whole section, at body level, so a screen
+      reader met it at the section's last line rather than at the
+      heading. Moreover, the docx reader looks for bookmarks only
+      inside paragraphs, so the ids did not round-trip.
+    + Improve highlighting of `marked` spans containing math (#11885,
+      Samuel Huang).
+
+  * OpenDocument writer:
+
+    + Use `literal` instead of `text . T.unpack`.
+    + Emit whitespace runs in one piece.
+    + Count styles and notes in O(1).
+    + Give nested notes distinct ids.
+    + Use valid column style names past column 26 (`AA`, `AB`, etc.).
+    + Apply the requested style to `Plain` blocks. `withParagraphStyle`
+      only wrapped `Para` in a paragraph with the requested
+      style; a `Plain` fell through to `blockToOpenDocument` and came
+      out with the default style.
+    + Render the abstract as blocks. The abstract was wrapped in a Div
+      with `custom-style` set to `Abstract`, but then handed to
+      `metaToContext`, which renders metadata fields with the inline writer.
+      The style was lost, as well as any block-level formatting.
+      Regression from 013351f602.
+    + Reuse identical automatic paragraph styles. Every blockquote,
+      every explicitly aligned table cell, and every direction-adjusted
+      style got a fresh `Pn` automatic style, even when an
+      identical one already existed.
+    + Look up cross-reference targets in a Map. Skip the collecting walk
+      altogether unless `xrefs_name` or `xrefs_number` is enabled, since
+      nothing reads the result otherwise and neither is on by default.
+      Elements with an empty identifier are no longer collected, so a link to
+      "#" can no longer resolve to a reference with an empty ref-name.
+    + Sort automatic text styles numerically rather than alphabetically
+      (`T9`, `T10`, ...).
+
+  * ODT writer:
+
+    + Always render `content.xml` with a template. `writeOpenDocument`
+      returns a bare body fragment when writerTemplate is
+      Nothing.  This is needed mainly for the `fodt` writer.
+    + Have `pandocToODT` return an Archive instead of a ByteString.
+      This lets an alternative entry point post-process the archive.
+    + Add `writeFODT` for flat OpenDocument output [API change].
+
+  * LaTeX writer:
+
+    + Allow alt text in figure to wrap (#11924).
+
+  * Commonmark writer:
+
+    + Fix escaping bug (#11927). A non-alphanumeric after an
+      escaped backslash would be omitted.
+
+  * Typst writer:
+
+    + Revert pandoc 3.12 change that omitted a blank line at the end
+      of a `block`. The blank line actually is semantically significant;
+      without it, styling of `par` will have no effect.
+    + Support `hanging-indent` and `entry-spacing` in CSL bibliography
+      entries (#11926). Instead of hard-coding the formatting in
+      the block, we use a show rule on `<refs>`, included
+      conditionally by the default template. Values will be set
+      for `csl-hanging-indent` and `csl-entry-spacing` based on
+      the CSL style, but these variables can be overridden on the
+      command line using `--variable`. It is also possible to
+      include a new show rule in header-includes, which will take
+      priority over the other.
+
+  * Markdown writer:
+
+    + Put `<..>` around link or image destinations containing
+      spaces (rca-umb).
+    + Escape list markers after a line break (#11863, Aslak Hellesøy).
+    + Don't use a multiline table for a header-only table (#11939).
+
+  * Text.Pandoc.XML:
+
+    + Make `escapeStringForXML` and `escapeNls` more efficient.
+    + Build XML tags with a single doclayout allocation rather than
+      several.
+
+  * Text.Pandoc.SelfContained:
+
+    + Fix regression in escaping of data URIs (#11942). A change
+      in 3.12 broke escaping of data URIs, so that `--embed-resources`
+      no longer worked properly.
+
+  * Text.Pandoc.Parsing:
+
+    + `mathDisplay`, `mathInline`: respect TeX groups and comments (#11887,
+      Erik Demaine). Prevent math delimiters inside TeX brace groups
+      and percent comments from prematurely closing an equation.
+
+  * Text.Pandoc.XML.Light:
+
+    + Build the root element from the event stream.
+      Instead of using xml-conduit's DOM parser and then converting
+      the result back to our Element type, just reuse
+      `parseXMLContentsWithEntities`, which folds the event stream
+      directly into our types. This speeds up every XML-based reader.
+      Behavior change: Attributes now preserve document order instead of
+      being sorted alphabetically, as was already the case for
+      `parseXMLContentsWithEntities`.
+
+  * Bump version of reveal.js to 6. Version 6 is required for the
+    changes in plugin locations incorporated in pandoc 3.12.
+
+  * `reference.docx`: don't make Table style `semiHidden` (#11931).
+    This allows it to appear in the table styles gallery.
+
+  * Benchmark improvements:
+
+    + Include the benchmark sample in the benchmark directory, to ensure
+      that it doesn't change between releases. Include images too.
+    + Change the benchmark sample to a mix of markup-heavy and prose-heavy
+      text, including tables.
+
+  * Use latest citeproc, texmath.
+
+## 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
+    still functional) options `--mathml`, `--mathjax`, `--gladtex`,
+    `--katex`, `--webtex`. The `plain` style can now be specified
+    explicitly. In defaults files, `html-math-method` is now
+    `math-method` (though `html-math-method` will still work).
+
+  * Make `mathml` the default math-method (#11751).
+
+  * Add `--completion={bash,zsh,fish}` (#8542, wzy).
+    Make `--bash-completion` an alias of `--completion=bash`.
+    See the manual for instructions on how to use these completions.
+
+  * Text.Pandoc.Options [API change]:
+
+    - Rename HTMLMathMethod type to MathMethod.
+    - Rename `writerHTMLMathMethod` field of WriterOptions to
+      `writerMathMethod`.
+
+  * Markdown reader:
+
+    + Add a syntax `(1@label)` for resetting example list counter
+      (#10940). This is needed often at the beginning of a chapter.
+
+  * MediaWiki writer:
+
+    + Put `<nowiki>` around possible URLs (#11834). Otherwise they get
+      linkified automatically.
+
+  * Docx reader:
+
+    + Handle case where non-header rows come before header rows (#11833).
+    + Improve handling of tables with uneven rows (#11833).
+
+  * RTF Reader:
+
+    + Add support for nested tables (#11218, Alex).
+
+  * Typst reader:
+
+    + Remove handling of 'block' as an inline-level element (#11814).
+
+  * HTML reader:
+
+    + Handle `pre` without `code` (#11810). Preserve whitespace as
+      nonbreaking spaces.
+
+  * ODT/OpenDocument writers:
+
+    + Support RTL text direction (#11301). RTL is now properly handled
+      in these cases: `dir: rtl` (or `ltr`) in document metadata; an
+      RTL `lang` in metadata (e.g. `he`, `ar`), unless overridden by
+      `dir`; a `dir` attribute on a Div. Code blocks remain ltr
+      regardless.
+    + Use `style:language-complex` rather than `fo:language` for RTL
+      languages (#11301).
+    + 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/docx/word/styles.xml b/data/docx/word/styles.xml
--- a/data/docx/word/styles.xml
+++ b/data/docx/word/styles.xml
@@ -591,8 +591,6 @@
   <w:style w:type="table" w:default="1" w:styleId="Table">
     <w:name w:val="Table" />
     <w:basedOn w:val="TableNormal" />
-    <w:semiHidden />
-    <w:unhideWhenUsed />
     <w:qFormat />
     <w:tblPr>
       <w:tblInd w:w="0" w:type="dxa" />
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/default.typst b/data/templates/default.typst
--- a/data/templates/default.typst
+++ b/data/templates/default.typst
@@ -22,6 +22,18 @@
 $highlighting-definitions$
 
 $endif$
+$if(csl-settings)$
+// formatting called for by CSL style
+#show <refs>: set par(
+$if(csl-hanging-indent)$
+  hanging-indent: $csl-hanging-indent$,
+$endif$
+$if(csl-entry-spacing)$
+  spacing: $csl-entry-spacing$,
+$endif$
+)
+
+$endif$
 $if(template)$
 #import "$template$": conf
 $else$
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.1
 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
@@ -227,6 +227,7 @@
                  test/command/11113.docx
                  test/command/11689.docx
                  test/command/11833.docx
+                 test/command/11942.js
                  test/command/biblio.bib
                  test/command/averroes.bib
                  test/command/A.txt
@@ -281,6 +282,11 @@
                  test/command/11486/scroll.revealjs
                  test/command/11498.png
                  test/command/11301-styles.opendocument
+                 test/command/opendocument-abstract.opendocument
+                 test/command/opendocument-body.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
@@ -450,20 +456,25 @@
                  test/twiki-reader.twiki
                  test/tikiwiki-reader.tikiwiki
                  test/odt/odt/*.odt
+                 test/odt/fodt/*.fodt
                  test/odt/markdown/*.md
                  test/odt/native/*.native
+                 test/odt/fodt-native/*.native
                  test/xlsx-reader/*.xlsx
                  test/xlsx-reader/*.native
                  test/pod-reader.pod
                  test/vimdoc/*.markdown
                  test/vimdoc/*.vimdoc
+                 benchmark/sample.md
+                 benchmark/movie.jpg
+                 benchmark/lalune.jpg
 source-repository head
   type:          git
   location:      https://github.com/jgm/pandoc.git
 
 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 +509,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 +538,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.1   && < 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 +563,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.1 && < 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 +735,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 +783,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 +791,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 +852,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 +866,19 @@
                   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
+                  xml-light,
+                  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
@@ -914,6 +925,7 @@
                   Tests.Writers.AsciiDoc
                   Tests.Writers.LaTeX
                   Tests.Writers.Docx
+                  Tests.Writers.ODT
                   Tests.Writers.RST
                   Tests.Writers.TEI
                   Tests.Writers.Markua
@@ -934,7 +946,8 @@
   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,
+                   temporary,
                    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/App/OutputSettings.hs b/src/Text/Pandoc/App/OutputSettings.hs
--- a/src/Text/Pandoc/App/OutputSettings.hs
+++ b/src/Text/Pandoc/App/OutputSettings.hs
@@ -32,7 +32,8 @@
 import Data.List (find)
 import Data.Maybe (catMaybes, fromMaybe)
 import Skylighting (defaultSyntaxMap)
-import Skylighting.Parser (addSyntaxDefinition, parseSyntaxDefinition)
+import Skylighting.Parser (addSyntaxDefinition, parseSyntaxDefinition,
+                           resolveKeywords)
 import System.Directory (getCurrentDirectory)
 import System.Exit (exitSuccess)
 import System.FilePath
@@ -161,10 +162,13 @@
         res <- liftIO (parseSyntaxDefinition f)
         case res of
               Left errstr -> throwError $ PandocSyntaxMapError $ T.pack errstr
-              Right syn   -> return $ addSyntaxDefinition syn existingmap
+              Right syn   -> return $
+                addSyntaxDefinition syn existingmap
 
-  syntaxMap <- foldM addSyntaxMap defaultSyntaxMap
-                     (optSyntaxDefinitions opts)
+  syntaxMap <- case optSyntaxDefinitions opts of
+                  [] -> return defaultSyntaxMap
+                  sdefs -> (\m -> M.map (resolveKeywords m) m) <$>
+                            foldM addSyntaxMap defaultSyntaxMap sdefs
 
   hlStyle <- case optSyntaxHighlighting opts of
     NoHighlightingString        -> pure NoHighlighting
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
@@ -188,7 +188,7 @@
 metaValueToNames x = maybeToList $ metaValueToName x
 
 metaValueToName :: MetaValue -> Maybe Name
-metaValueToName (MetaMap m) = extractParticles <$>
+metaValueToName (MetaMap m) =
   Just Name
     { nameFamily = family
     , nameGiven = given
@@ -212,7 +212,7 @@
   staticordering = fromMaybe False $
     M.lookup "static-ordering" m >>= metaValueToBool
   literal = M.lookup "literal" m >>= metaValueToText
-metaValueToName x = extractParticles <$>
+metaValueToName x =
   case metaValueToText x of
     Nothing -> Nothing
     Just lit -> Just Name
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/Extensions.hs b/src/Text/Pandoc/Extensions.hs
--- a/src/Text/Pandoc/Extensions.hs
+++ b/src/Text/Pandoc/Extensions.hs
@@ -543,6 +543,7 @@
     , Ext_xrefs_number
     ]
   getAll "odt"             = getAll "opendocument" <> autoIdExtensions
+  getAll "fodt"            = getAll "odt"
   getAll "muse"            = autoIdExtensions <>
     extensionsFromList
     [ Ext_amuse ]
diff --git a/src/Text/Pandoc/Format.hs b/src/Text/Pandoc/Format.hs
--- a/src/Text/Pandoc/Format.hs
+++ b/src/Text/Pandoc/Format.hs
@@ -187,6 +187,7 @@
     ".dokuwiki" -> defFlavor "dokuwiki"
     ".epub"     -> defFlavor "epub"
     ".fb2"      -> defFlavor "fb2"
+    ".fodt"     -> defFlavor "fodt"
     ".htm"      -> defFlavor "html"
     ".html"     -> defFlavor "html"
     ".icml"     -> defFlavor "icml"
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/MIME.hs b/src/Text/Pandoc/MIME.hs
--- a/src/Text/Pandoc/MIME.hs
+++ b/src/Text/Pandoc/MIME.hs
@@ -154,6 +154,7 @@
            ,("fbdoc","application/x-maker")
            ,("fch","chemical/x-gaussian-checkpoint")
            ,("fchk","chemical/x-gaussian-checkpoint")
+           ,("fodt","application/vnd.oasis.opendocument.text-flat-xml")
            ,("frm","application/x-maker")
            ,("fs","text/plain")
            ,("gal","chemical/x-gaussian-log")
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,9 +15,10 @@
   )
 where
 
-import Control.Monad (mzero, when)
+import Control.Monad (when, guard)
 import Data.Text (Text)
-import Text.Parsec ((<|>), ParsecT, Stream(..), notFollowedBy, many1, try)
+import Text.Parsec ((<|>), ParsecT, Stream(..), notFollowedBy, manyTill,
+                    many1, try)
 import Text.Pandoc.Options
   ( Extension(Ext_tex_math_dollars, Ext_tex_math_single_backslash,
               Ext_tex_math_double_backslash) )
@@ -25,78 +26,80 @@
 import Text.Pandoc.Parsing.General
 import Text.Pandoc.Shared (trimMath)
 import Text.Pandoc.Sources
-  (UpdateSourcePos, anyChar, char, digit, newline, satisfy, space, string)
+  (UpdateSourcePos, anyChar, char, digit, newline, satisfy, space)
 
 import qualified Data.Text as T
 import qualified Data.Text.Lazy as TL
 import qualified Data.Text.Lazy.Builder as TB
 
-mathInlineWith :: (Stream s m Char, UpdateSourcePos s Char)  => Text -> Text -> ParsecT s st m Text
-mathInlineWith op cl = try $ do
+mathWith :: (Stream s m Char, UpdateSourcePos s Char)
+               => Text -> Text -> ParsecT s st m Text
+mathWith op cl = try $ do
   textStr op
   when (op == "$") $ notFollowedBy space
   words' <- many1Till (
-                       (T.singleton <$>
-                          satisfy (\c -> not (isSpaceChar c || c == '\\')))
-                   <|> (char '\\' >>
-                           -- This next clause is needed because \text{..} can
-                           -- contain $, \(\), etc.
-                           (try (string "text" >>
-                                 (("\\text" <>) <$> inBalancedBraces 0 ""))
-                            <|>  (\c -> T.pack ['\\',c]) <$> anyChar))
-                   <|> ("\n" <$ blankline <* notFollowedBy' blankline <* notFollowedBy (char '$'))
-                   <|> (T.pack <$> many1 spaceChar <* notFollowedBy (char '$'))
+                       mathComment
+                   <|> mathGroup
+                   <|> (T.singleton <$>
+                          satisfy (\c -> not
+                                    (isSpaceChar c || c == '\\' || c == '{')))
+                   <|> (T.pack <$> many1 spaceChar <*
+                          (guard (op /= "$") <|> notFollowedBy (char '$')))
+                   <|> ("\n" <$ newline <* notFollowedBy' blankline <*
+                          (guard (op /= "$") <|> notFollowedBy (char '$')))
+                   <|> (char '\\' >> (\c -> T.pack ['\\',c]) <$> anyChar)
                     ) (try $ textStr cl)
-  notFollowedBy digit  -- to prevent capture of $5
+  when (cl == "$") $ notFollowedBy digit  -- to prevent capture of $5
   return $ trimMath $ T.concat words'
- where
-  inBalancedBraces :: (Stream s m Char, UpdateSourcePos s Char)
-                   => Int -> Text -> ParsecT s st m Text
-  inBalancedBraces n t =
-    TL.toStrict . TB.toLazyText <$>
-      go n False (TB.fromText t) (not (T.null t))
 
-  -- go depth lastWasBackslash accumulator started
-  go :: (Stream s m Char, UpdateSourcePos s Char)
-     => Int -> Bool -> TB.Builder -> Bool -> ParsecT s st m TB.Builder
-  go 0 _ acc False = do
-    c <- anyChar
-    if c == '{'
-       then go 1 False (acc <> TB.singleton '{') True
-       else mzero
-  go 0 _ acc True = return acc
-  go depth True acc _ = do
-    c <- anyChar
-    go depth False (acc <> TB.singleton c) True
-  go depth False acc _ = do
-    c <- anyChar
-    let acc' = acc <> TB.singleton c
-    case c of
-         '\\' -> go depth True acc' True
-         '}'  -> go (depth - 1) False acc' True
-         '{'  -> go (depth + 1) False acc' True
-         _    -> go depth False acc' True
+-- Preserve comments, but do not interpret their braces or math delimiters.
+-- Consume the newline too: it terminates the comment, not the math content.
+mathComment :: (Stream s m Char, UpdateSourcePos s Char)
+            => ParsecT s st m Text
+mathComment = do
+  char '%'
+  content <- manyTill anyChar newline
+  notFollowedBy' blankline
+  return $ T.pack ('%' : content ++ "\n")
 
-mathDisplayWith :: (Stream s m Char, UpdateSourcePos s Char) => Text -> Text -> ParsecT s st m Text
-mathDisplayWith op cl = try $ fmap T.pack $ do
-  textStr op
-  many1Till (satisfy (/= '\n') <|> (newline <* notFollowedBy' blankline))
-            (try $ textStr cl)
+-- Consume a whole TeX group so delimiters in command arguments (for example,
+-- \text{hi $x$ bye} or \colorbox{aqua}{$x$}) cannot close the outer math.
+-- Escaped braces do not change the nesting depth.
+mathGroup :: (Stream s m Char, UpdateSourcePos s Char) => ParsecT s st m Text
+mathGroup = do
+  char '{'
+  TL.toStrict . TB.toLazyText <$> go (1 :: Int) False (TB.singleton '{')
+ where
+  -- go depth lastWasBackslash accumulator
+  go 0 _ acc = return acc
+  go depth True acc = do
+    c <- mathChar
+    go depth False (acc <> TB.singleton c)
+  go depth False acc =
+    (do comment <- mathComment
+        go depth False (acc <> TB.fromText comment))
+    <|> do
+      c <- mathChar
+      let acc' = acc <> TB.singleton c
+      case c of
+           '\\' -> go depth True acc'
+           '}'  -> go (depth - 1) False acc'
+           '{'  -> go (depth + 1) False acc'
+           _    -> go depth False acc'
 
+mathChar :: (Stream s m Char, UpdateSourcePos s Char) => ParsecT s st m Char
+mathChar = satisfy (/= '\n') <|> (newline <* notFollowedBy' blankline)
+
 mathDisplay :: (HasReaderOptions st, Stream s m Char, UpdateSourcePos s Char)
             => ParsecT s st m Text
 mathDisplay =
-      (guardEnabled Ext_tex_math_dollars >> mathDisplayWith "$$" "$$")
-  <|> (guardEnabled Ext_tex_math_single_backslash >>
-       mathDisplayWith "\\[" "\\]")
-  <|> (guardEnabled Ext_tex_math_double_backslash >>
-       mathDisplayWith "\\\\[" "\\\\]")
+      (guardEnabled Ext_tex_math_dollars >> mathWith "$$" "$$")
+  <|> (guardEnabled Ext_tex_math_single_backslash >> mathWith "\\[" "\\]")
+  <|> (guardEnabled Ext_tex_math_double_backslash >> mathWith "\\\\[" "\\\\]")
 
 mathInline :: (HasReaderOptions st, Stream s m Char, UpdateSourcePos s Char)
            => ParsecT s st m Text
 mathInline =
-      (guardEnabled Ext_tex_math_dollars >> mathInlineWith "$" "$")
-  <|> (guardEnabled Ext_tex_math_single_backslash >>
-       mathInlineWith "\\(" "\\)")
-  <|> (guardEnabled Ext_tex_math_double_backslash >>
-       mathInlineWith "\\\\(" "\\\\)")
+      (guardEnabled Ext_tex_math_dollars >> mathWith "$" "$")
+  <|> (guardEnabled Ext_tex_math_single_backslash >> mathWith "\\(" "\\)")
+  <|> (guardEnabled Ext_tex_math_double_backslash >> mathWith "\\\\(" "\\\\)")
diff --git a/src/Text/Pandoc/Readers.hs b/src/Text/Pandoc/Readers.hs
--- a/src/Text/Pandoc/Readers.hs
+++ b/src/Text/Pandoc/Readers.hs
@@ -30,6 +30,7 @@
   , readPptx
   , readXlsx
   , readODT
+  , readFODT
   , readMarkdown
   , readCommonMark
   , readCreole
@@ -167,6 +168,7 @@
           ,("pptx"         , ByteStringReader readPptx)
           ,("xlsx"         , ByteStringReader readXlsx)
           ,("odt"          , ByteStringReader readODT)
+          ,("fodt"         , TextReader readFODT)
           ,("t2t"          , TextReader readTxt2Tags)
           ,("epub"         , ByteStringReader readEPUB)
           ,("muse"         , TextReader readMuse)
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'') =
@@ -167,6 +167,8 @@
          [ (superscriptSpec <>) | isEnabled Ext_superscript opts ] ++
          [ (subscriptSpec <>) | isEnabled Ext_subscript opts ] ++
          [ (mathSpec <>) | isEnabled Ext_tex_math_dollars opts ] ++
+         [ (taskListSpec <>) | isEnabled Ext_task_lists opts ] ++
+         -- note: task_list before fancy_list, work around jgm/commonmark#180
          [ (fancyListSpec <>) | isEnabled Ext_fancy_lists opts ] ++
          [ (fencedDivSpec <>) | isEnabled Ext_fenced_divs opts ] ++
          [ (bracketedSpanSpec <>) | isEnabled Ext_bracketed_spans opts ] ++
@@ -187,7 +189,6 @@
            | isEnabled Ext_implicit_header_references opts ] ++
          [ (footnoteSpec <>) | isEnabled Ext_footnotes opts ] ++
          [ (definitionListSpec <>) | isEnabled Ext_definition_lists opts ] ++
-         [ (taskListSpec <>) | isEnabled Ext_task_lists opts ] ++
          [ (wikilinksSpec TitleAfterPipe <>)
            | isEnabled Ext_wikilinks_title_after_pipe opts ] ++
          [ (wikilinksSpec TitleBeforePipe <>)
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
@@ -62,6 +62,33 @@
 specialChars :: [Char]
 specialChars = "*/~{}\\|[]()<>\"'"
 
+-- | True for characters that terminate a run of plain text, i.e. the
+-- 'specialChars' plus tabs, newlines and spaces.  Written as a @case@ so
+-- that GHC compiles it to a jump table; this is applied to every single
+-- character of the input, so a linear scan over the list here is a
+-- significant cost.  Must be kept in sync with 'specialChars'.
+isStrEnd :: Char -> Bool
+isStrEnd c = case c of
+  '\t' -> True
+  '\n' -> True
+  ' '  -> True
+  '"'  -> True
+  '\'' -> True
+  '('  -> True
+  ')'  -> True
+  '*'  -> True
+  '/'  -> True
+  '<'  -> True
+  '>'  -> True
+  '['  -> True
+  '\\' -> True
+  ']'  -> True
+  '{'  -> True
+  '|'  -> True
+  '}'  -> True
+  '~'  -> True
+  _    -> False
+
 parseCreole :: PandocMonad m => CRLParser m Pandoc
 parseCreole = do
   bs <- mconcat <$> many block
@@ -91,7 +118,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 +242,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 +256,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 "[["
@@ -272,8 +299,7 @@
 symbol = fmap (B.str . T.singleton) (oneOf specialChars)
 
 str :: PandocMonad m => CRLParser m B.Inlines
-str = let strChar = noneOf ("\t\n " ++ specialChars) in
-        fmap B.str (many1Char strChar)
+str = fmap B.str (takeWhile1P (not . isStrEnd))
 
 bold :: PandocMonad m => CRLParser m B.Inlines
 bold = B.strong . mconcat <$>
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]
@@ -311,7 +312,9 @@
         | Just True <- isSmallCaps rPr =
             smallcaps . go rPr{isSmallCaps = Nothing}
         | Just True <- isStrike rPr =
-            strikeout . go rPr{isStrike = Nothing}
+            strikeout . go rPr{isStrike = Nothing, isDoubleStrike = Nothing}
+        | Just True <- isDoubleStrike rPr =
+            strikeout . go rPr{isDoubleStrike = Nothing}
         | Just True <- isRTL rPr =
             spanWith ("",[],[("dir","rtl")]) . go rPr{isRTL = Nothing}
         | inBidi, Just False <- isRTL rPr =
@@ -411,14 +414,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 +465,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 +486,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 +612,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 +859,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/Combine.hs b/src/Text/Pandoc/Readers/Docx/Combine.hs
--- a/src/Text/Pandoc/Readers/Docx/Combine.hs
+++ b/src/Text/Pandoc/Readers/Docx/Combine.hs
@@ -80,7 +80,28 @@
   let (fs, ils') = unstackInlines ils
       (left, (right, contents')) = second (spanr isSpace) $ spanl isSpace $ unMany ils'
       -- NOTE: spanr counterintuitively returns suffix as the FIRST tuple element
-  in (Many left, (fs, Many contents'), Many right)
+  in case breakAtTrackedChange fs of
+       -- A space at the edge of an inserted or deleted run belongs to the
+       -- change: moving it out of the span would lose it when the change is
+       -- accepted or rejected later (#4427). It is still moved out of the
+       -- formatting inside the change.
+       Just (outer, inner) ->
+         (mempty, (outer, Many left <> stackInlines inner (Many contents')
+                           <> Many right), mempty)
+       Nothing -> (Many left, (fs, Many contents'), Many right)
+
+-- | Split a modifier stack (outermost first) after its innermost
+-- tracked-change span, if it has one.
+breakAtTrackedChange :: [Modifier a] -> Maybe ([Modifier a], [Modifier a])
+breakAtTrackedChange fs =
+  case L.findIndex isTrackedChange (reverse fs) of
+    Nothing -> Nothing
+    Just i  -> Just (splitAt (length fs - i) fs)
+
+isTrackedChange :: Modifier a -> Bool
+isTrackedChange (AttrModifier _ (_, classes, _)) =
+  any (`elem` classes) ["insertion", "deletion"]
+isTrackedChange _ = False
 
 isSpace :: Inline -> Bool
 isSpace Space = True
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
@@ -109,6 +109,7 @@
                            , envFont          :: Maybe Font
                            , envCharStyles    :: CharStyleMap
                            , envParStyles     :: ParStyleMap
+                           , envTableStyles   :: TableStyleMap
                            , envLocation      :: DocumentLocation
                            , envDocXmlPath    :: FilePath
                            , envTextWidth     :: Int
@@ -233,6 +234,8 @@
 
 type ParStyleMap = M.Map ParaStyleId ParStyle
 
+type TableStyleMap = M.Map TableStyleId TableStyle
+
 data Numbering = Numbering NameSpaces [Numb] [AbstractNumb]
                  deriving Show
 
@@ -382,6 +385,7 @@
     , isItalicCTL = isItalicCTL a <|> isItalicCTL b
     , isSmallCaps = isSmallCaps a <|> isSmallCaps b
     , isStrike = isStrike a <|> isStrike b
+    , isDoubleStrike = isDoubleStrike a <|> isDoubleStrike b
     , isRTL = isRTL a <|> isRTL b
     , isForceCTL = isForceCTL a <|> isForceCTL b
     , rHighlight = rHighlight a <|> rHighlight b
@@ -398,8 +402,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
@@ -443,7 +447,7 @@
       numbering = archiveToNumbering archive
       rels      = archiveToRelationships archive docXmlPath
       media     = filteredFilesFromArchive archive filePathIsMedia
-      (styles, parstyles) = archiveToStyles archive
+      (styles, parstyles, tabstyles) = archiveToStyles archive
       textWidth = archiveToTextWidth archive
       rEnv = ReaderEnv { envNotes = notes
                        , envComments = comments
@@ -453,6 +457,7 @@
                        , envFont = Nothing
                        , envCharStyles = styles
                        , envParStyles = parstyles
+                       , envTableStyles = tabstyles
                        , envLocation = InDocument
                        , envDocXmlPath = docXmlPath
                        , envTextWidth = fromMaybe 9360 textWidth
@@ -512,8 +517,8 @@
       | otherwise
       = ((:[]) <$> elemToBodyPart ns' el) `catchError` (\_ -> return [])
 
-archiveToStyles :: Archive -> (CharStyleMap, ParStyleMap)
-archiveToStyles = archiveToStyles' getStyleId getStyleId
+archiveToStyles :: Archive -> (CharStyleMap, ParStyleMap, TableStyleMap)
+archiveToStyles = archiveToStyles' getStyleId getStyleId getStyleId
 
 class HasParentStyle a where
   getParentStyle :: a -> Maybe a
@@ -1217,18 +1222,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
@@ -1504,10 +1511,13 @@
 
 stripCaptionLabel :: [Element] -> [Element]
 stripCaptionLabel els =
-  if any isNumberElt els
-     then dropWhile (not . isNumberElt) els
-     else els
+    if null afters
+       then els
+       else filter isBookmark befores <> afters
   where
+    (befores, afters) = break isNumberElt els
+    isBookmark (Element name _ _ _) =
+      qName name == "bookmarkStart" || qName name == "bookmarkEnd"
     isNumberElt el@(Element name attribs _ _) =
        (qName name == "fldSimple" &&
              case lookupAttrBy ((== "instr") . qName) attribs of
diff --git a/src/Text/Pandoc/Readers/Docx/Parse/Styles.hs b/src/Text/Pandoc/Readers/Docx/Parse/Styles.hs
--- a/src/Text/Pandoc/Readers/Docx/Parse/Styles.hs
+++ b/src/Text/Pandoc/Readers/Docx/Parse/Styles.hs
@@ -21,12 +21,15 @@
   , CharStyle
   , ParaStyleId(..)
   , ParStyle(..)
+  , TableStyleId(..)
+  , TableStyle
   , ParIndentation(..)
   , RunStyle(..)
   , HasStyleName
   , StyleName
   , ParaStyleName
   , CharStyleName
+  , TableStyleName
   , FromStyleName
   , VertAlign(..)
   , StyleId
@@ -62,11 +65,15 @@
   deriving (Show, Eq, Ord, IsString, FromStyleId)
 newtype ParaStyleId   = ParaStyleId T.Text
   deriving (Show, Eq, Ord, IsString, FromStyleId)
+newtype TableStyleId   = TableStyleId T.Text
+  deriving (Show, Eq, Ord, IsString, FromStyleId)
 
 newtype CharStyleName = CharStyleName CIString
   deriving (Show, Eq, Ord, IsString, FromStyleName)
 newtype ParaStyleName = ParaStyleName CIString
   deriving (Show, Eq, Ord, IsString, FromStyleName)
+newtype TableStyleName = TableStyleName CIString
+  deriving (Show, Eq, Ord, IsString, FromStyleName)
 
 -- Case-insensitive comparisons
 newtype CIString = CIString T.Text deriving (Show, IsString, FromStyleName)
@@ -103,18 +110,19 @@
                            , cStyleData :: RunStyle
                            } deriving (Show)
 
-data RunStyle = RunStyle { isBold       :: Maybe Bool
-                         , isBoldCTL    :: Maybe Bool
-                         , isItalic     :: Maybe Bool
-                         , isItalicCTL  :: Maybe Bool
-                         , isSmallCaps  :: Maybe Bool
-                         , isStrike     :: Maybe Bool
-                         , isRTL        :: Maybe Bool
-                         , isForceCTL   :: Maybe Bool
-                         , rHighlight   :: Maybe Text
-                         , rVertAlign   :: Maybe VertAlign
-                         , rUnderline   :: Maybe Text
-                         , rParentStyle :: Maybe CharStyle
+data RunStyle = RunStyle { isBold         :: Maybe Bool
+                         , isBoldCTL      :: Maybe Bool
+                         , isItalic       :: Maybe Bool
+                         , isItalicCTL    :: Maybe Bool
+                         , isSmallCaps    :: Maybe Bool
+                         , isStrike       :: Maybe Bool
+                         , isDoubleStrike :: Maybe Bool
+                         , isRTL          :: Maybe Bool
+                         , isForceCTL     :: Maybe Bool
+                         , rHighlight     :: Maybe Text
+                         , rVertAlign     :: Maybe VertAlign
+                         , rUnderline     :: Maybe Text
+                         , rParentStyle   :: Maybe CharStyle
                          }
                 deriving Show
 
@@ -132,6 +140,10 @@
                          }
                     deriving Show
 
+data TableStyle = TableStyle { tStyleName  :: TableStyleName
+                             , tStyleId  :: TableStyleId }
+                    deriving Show
+
 defaultRunStyle :: RunStyle
 defaultRunStyle = RunStyle { isBold = Nothing
                            , isBoldCTL = Nothing
@@ -139,6 +151,7 @@
                            , isItalicCTL = Nothing
                            , isSmallCaps = Nothing
                            , isStrike = Nothing
+                           , isDoubleStrike = Nothing
                            , isRTL = Nothing
                            , isForceCTL = Nothing
                            , rHighlight = Nothing
@@ -148,21 +161,24 @@
                            }
 
 archiveToStyles'
-  :: (Ord k1, Ord k2, ElemToStyle a1, ElemToStyle a2)
-  => (a1 -> k1) -> (a2 -> k2) -> Archive -> (M.Map k1 a1, M.Map k2 a2)
-archiveToStyles' conv1 conv2 zf =
+  :: (Ord k1, Ord k2, Ord k3, ElemToStyle a1, ElemToStyle a2, ElemToStyle a3)
+  => (a1 -> k1) -> (a2 -> k2) -> (a3 -> k3)
+  -> Archive -> (M.Map k1 a1, M.Map k2 a2, M.Map k3 a3)
+archiveToStyles' conv1 conv2 conv3 zf =
   case findEntryByPath "word/styles.xml" zf of
-    Nothing -> (M.empty, M.empty)
+    Nothing -> (M.empty, M.empty, M.empty)
     Just entry ->
       case parseXMLElement . UTF8.toTextLazy . fromEntry $ entry of
-        Left _ -> (M.empty, M.empty)
+        Left _ -> (M.empty, M.empty, M.empty)
         Right styElem ->
           let namespaces = elemToNameSpaces styElem
           in
            ( M.fromList $ map (\r -> (conv1 r, r)) $
                buildBasedOnList namespaces styElem Nothing,
              M.fromList $ map (\p -> (conv2 p, p)) $
-               buildBasedOnList namespaces styElem Nothing)
+               buildBasedOnList namespaces styElem Nothing,
+             M.fromList $ map (\p -> (conv3 p, p)) $
+               buildBasedOnList namespaces styElem Nothing )
 
 isBasedOnStyle :: (ElemToStyle a, FromStyleId (StyleId a)) => NameSpaces -> Element -> Maybe a -> Bool
 isBasedOnStyle ns element parentStyle
@@ -223,6 +239,22 @@
   type StyleName ParStyle = ParaStyleName
   getStyleName = pStyleName
 
+instance ElemToStyle TableStyle where
+  cStyleType _ = "table"
+  elemToStyle ns element parentStyle
+    | isElem ns "w" "style" element
+    , Just "table" <- findAttrByName ns "w" "type" element
+    = elemToTableStyleData ns element parentStyle
+    | otherwise = Nothing
+
+instance HasStyleId TableStyle where
+  type StyleId TableStyle = TableStyleId
+  getStyleId = tStyleId
+
+instance HasStyleName TableStyle where
+  type StyleName TableStyle = TableStyleName
+  getStyleName = tStyleName
+
 getStyleChildren :: (ElemToStyle a) => NameSpaces -> Element -> Maybe a -> [a]
 getStyleChildren ns element parentStyle
   | isElem ns "w" "styles" element =
@@ -275,6 +307,7 @@
       , isItalicCTL = checkOnOff ns rPr (elemName ns "w" "iCs")
       , isSmallCaps = checkOnOff ns rPr (elemName ns "w" "smallCaps")
       , isStrike = checkOnOff ns rPr (elemName ns "w" "strike")
+      , isDoubleStrike = checkOnOff ns rPr (elemName ns "w" "dstrike")
       , isRTL = checkOnOff ns rPr (elemName ns "w" "rtl")
       , isForceCTL = checkOnOff ns rPr (elemName ns "w" "cs")
       , rHighlight =
@@ -350,3 +383,14 @@
       , pStyleId = ParaStyleId styleId
       }
 elemToParStyleData _ _ _ = Nothing
+
+elemToTableStyleData :: NameSpaces
+                     -> Element -> Maybe TableStyle -> Maybe TableStyle
+elemToTableStyleData ns element _parentStyle
+  | Just styleId <- findAttrByName ns "w" "styleId" element
+  , Just styleName <- getElementStyleName ns element
+  = Just $ TableStyle
+      { tStyleName = styleName
+      , tStyleId = TableStyleId styleId
+      }
+elemToTableStyleData _ _ _ = Nothing
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)
@@ -328,7 +329,8 @@
 
 kvPair :: PandocMonad m => Bool -> MarkdownParser m (Text, MetaValue)
 kvPair allowEmpty = try $ do
-  key <- many1TillChar (alphaNum <|> oneOf "_- ") (char ':')
+  key <- takeWhile1P (\c -> isAlphaNum c || c == '_' || c == '-' || c == ' ')
+  _ <- char ':'
   val <- trim <$> manyTillChar anyChar
           (try $ newline >> lookAhead (blankline <|> nonspaceChar))
   guard $ allowEmpty || not (T.null val)
@@ -412,7 +414,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
 
@@ -575,11 +577,18 @@
   skipSpaces
   return attr
 
+setextUnderline :: PandocMonad m => MarkdownParser m Char
+setextUnderline = do
+  underlineChar <- oneOf setextHChars
+  skipMany (char underlineChar)
+  blanklines
+  pure underlineChar
+
 setextHeader :: PandocMonad m => MarkdownParser m (F Blocks)
 setextHeader = try $ do
   -- This lookahead prevents us from wasting time parsing Inlines
   -- unless necessary -- it gives a significant performance boost.
-  lookAhead $ anyLine >> many1 (oneOf setextHChars) >> blankline
+  lookAhead $ anyLine >> setextUnderline
   skipSpaces
   (text, raw) <- withRaw $ do
     oldAllowLineBreaks <- stateAllowLineBreaks <$> getState
@@ -589,9 +598,7 @@
     updateState $ \st -> st{ stateAllowLineBreaks = oldAllowLineBreaks }
     return res
   attr <- setextHeaderEnd
-  underlineChar <- oneOf setextHChars
-  many (char underlineChar)
-  blanklines
+  underlineChar <- setextUnderline
   let level = fromMaybe 0 (elemIndex underlineChar setextHChars) + 1
   attr' <- registerHeader attr (runF text defaultParserState)
   guardDisabled Ext_implicit_header_references
@@ -660,7 +667,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)
@@ -694,7 +701,7 @@
   char '{'
   skipMany spaceChar
   char '='
-  format <- many1Char $ satisfy (\c -> isAlphaNum c || c `elem` ['-', '_'])
+  format <- takeWhile1P (\c -> isAlphaNum c || c `elem` ['-', '_'])
   skipMany spaceChar
   char '}'
   return format
@@ -711,8 +718,8 @@
      (Left <$> (guardEnabled Ext_raw_attribute >> try rawAttribute))
     <|>
      (Right <$> do
-         let pLangId = many1Char . satisfy $ \x ->
-               x `notElem` ['`', '{', '}'] && not (isSpace x)
+         let pLangId = takeWhile1P (\x ->
+               x `notElem` ['`', '{', '}'] && not (isSpace x))
          mbLanguageId <- optionMaybe (toLanguageId <$> pLangId)
          skipMany spaceChar
          mbAttr <- optionMaybe
@@ -814,12 +821,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 +837,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 +868,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 +980,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 +1026,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 +1234,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 +1323,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 +1410,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 +1546,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 +1660,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 +1696,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 +1796,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 +1811,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 +1875,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 +1886,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
@@ -1917,7 +1939,8 @@
 bracketedSpan = do
   guardEnabled Ext_bracketed_spans
   try $ do
-    (lab,_) <- reference
+    guardDisabled Ext_footnotes <|> notFollowedBy' noteMarker
+    lab <- inBalancedBrackets inlines
     attr <- attributes
     return $ wrapSpan attr <$> lab
 
@@ -2021,6 +2044,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 +2087,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 +2160,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 +2213,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 +2260,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
@@ -133,7 +133,9 @@
 anyText = escape <|> regularText <|> quoteChar <|> spaceTabChar
 
 regularText :: PandocMonad m => Lexer m T.Text
-regularText = many1Char $ noneOf "\n\r\t \\\""
+regularText = takeWhile1P (\c -> not (c == '\n' || c == '\r' ||
+                                      c == '\t' || c == ' ' ||
+                                      c == '\\' || c == '"'))
 
 quoteChar :: PandocMonad m => Lexer m T.Text
 quoteChar = T.singleton <$> char '"'
@@ -142,7 +144,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.
@@ -94,8 +94,31 @@
 specialChars :: [Char]
 specialChars = "'[]<=&*{}|\":\\_"
 
-spaceChars :: [Char]
-spaceChars = " \n\t"
+-- | True for characters that terminate a run of plain text, i.e. the
+-- 'specialChars' plus space, newline and tab.  Written as a @case@ so that
+-- GHC compiles it to a jump table; this is applied to every single
+-- character of the input, so a linear scan over the list here is a
+-- significant cost.  Must be kept in sync with 'specialChars'.
+isStrEnd :: Char -> Bool
+isStrEnd c = case c of
+  '\t' -> True
+  '\n' -> True
+  ' '  -> True
+  '"'  -> True
+  '&'  -> True
+  '\'' -> True
+  '*'  -> True
+  ':'  -> True
+  '<'  -> True
+  '='  -> True
+  '['  -> True
+  '\\' -> True
+  ']'  -> True
+  '_'  -> True
+  '{'  -> True
+  '|'  -> True
+  '}'  -> True
+  _    -> False
 
 sym :: PandocMonad m => Text -> MWParser m ()
 sym s = () <$ try (string $ T.unpack s)
@@ -299,7 +322,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 +348,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 +430,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 +639,7 @@
       <|> special
 
 str :: PandocMonad m => MWParser m Inlines
-str = B.str <$> many1Char (noneOf $ specialChars ++ spaceChars)
+str = B.str <$> takeWhile1P (not . isStrEnd)
 
 math :: PandocMonad m => MWParser m Inlines
 math = (B.displayMath . trim <$> try (many1 (char ':') >> textInTags "math"))
@@ -711,9 +736,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 +748,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 +769,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 +777,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
@@ -11,10 +11,11 @@
 Entry point to the odt reader.
 -}
 
-module Text.Pandoc.Readers.ODT ( readODT ) where
+module Text.Pandoc.Readers.ODT ( readODT, readFODT ) where
 
 import Codec.Archive.Zip
 import Text.Pandoc.XML.Light
+import qualified Text.Pandoc.XML.Light as XML
 import Text.Pandoc.Walk
 
 import Data.Char (isDigit)
@@ -22,9 +23,11 @@
 
 import System.FilePath
 
+import Control.Monad (unless)
 import Control.Monad.Except (throwError)
 
 import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
 
 import Text.Pandoc.Class.PandocMonad (PandocMonad)
 import qualified Text.Pandoc.Class.PandocMonad as P
@@ -40,6 +43,7 @@
 import Text.Pandoc.Readers.ODT.Generic.Fallible
 import Text.Pandoc.Readers.ODT.Generic.XMLConverter
 import Text.Pandoc.Shared (filteredFilesFromArchive)
+import Text.Pandoc.Sources (ToSources(..), sourcesToText)
 
 readODT :: PandocMonad m
         => ReaderOptions
@@ -51,6 +55,40 @@
     return $ walk makeFigure doc
   Left e -> throwError e
 
+-- | Read a flat OpenDocument text file, i.e. ODF's representation as a
+-- single XML file rather than a zip archive.
+readFODT :: (PandocMonad m, ToSources a)
+         => ReaderOptions
+         -> a
+         -> m Pandoc
+readFODT _ inp = do
+  root <- either (throwError . PandocXMLError "") pure
+            (parseXMLElement (TL.fromStrict (sourcesToText (toSources inp))))
+  case elementToODT root of
+    Right (doc, mb) -> do
+      P.setMediaBag mb
+      return $ walk makeFigure doc
+    Left e -> throwError e
+
+-- | A flat file has exactly one of each of the top-level elements that a
+-- package distributes over @content.xml@ and @styles.xml@, and they are
+-- all children of the root.  The converter addresses everything relative
+-- to the element it is given, so it can simply be handed that root.
+elementToODT :: Element -> Either PandocError (Pandoc, MediaBag)
+elementToODT root = do
+  -- the local name only, consistently with the URI-based namespace
+  -- matching downstream; office:document-content is correctly rejected
+  unless (XML.qName (XML.elName root) == "document") $
+    Left $ PandocParseError
+      "Expected office:document at the root of the flat OpenDocument file"
+  styles <- either
+               (\_ -> Left $ PandocParseError "Could not read styles")
+               Right
+               (readStylesAt root)
+  -- the media list is empty: all media arrives in office:binary-data
+  either (\_ -> Left $ PandocParseError "Could not convert opendocument") Right
+    (runConverter read_body (readerState styles []) root)
+
 -- the ODT parser uses old-style figures: an image with title beginning
 -- "fig:" in a paragraph by itself.  Convert these to new Figure elements.
 makeFigure :: Block -> Block
@@ -103,7 +141,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,1050 +1,1101 @@
-{-# LANGUAGE Arrows            #-}
-{-# LANGUAGE CPP               #-}
-{-# LANGUAGE DeriveFoldable    #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE PatternGuards     #-}
-{-# LANGUAGE RecordWildCards   #-}
-{-# LANGUAGE TupleSections     #-}
-{-# LANGUAGE ViewPatterns      #-}
-{-# LANGUAGE OverloadedStrings #-}
-{- |
-   Module      : Text.Pandoc.Readers.ODT.ContentReader
-   Copyright   : Copyright (C) 2015 Martin Linnemann
-   License     : GNU GPL, version 2 or above
-
-   Maintainer  : Martin Linnemann <theCodingMarlin@googlemail.com>
-   Stability   : alpha
-   Portability : portable
-
-The core of the odt reader that converts odt features into Pandoc types.
--}
-
-module Text.Pandoc.Readers.ODT.ContentReader
-( readerState
-, read_body
-) where
-
-import Prelude hiding (Applicative(..))
-import Control.Applicative hiding (liftA, liftA2, liftA3)
-import Control.Arrow
-import Control.Monad ((<=<))
-
-import qualified Data.ByteString.Lazy as B
-import Data.Foldable (fold)
-import Data.List (find)
-import qualified Data.Map as M
-import qualified Data.Text as T
-import Data.Maybe
-import Data.Monoid (Alt (..))
-
-import Text.TeXMath (readMathML, writeTeX)
-import qualified Text.Pandoc.XML.Light as XML
-
-import Text.Pandoc.Builder hiding (underline)
-import Text.Pandoc.MediaBag (MediaBag, insertMedia)
-import Text.Pandoc.Shared
-import Text.Pandoc.Extensions (extensionsFromList, Extension(..))
-import qualified Text.Pandoc.UTF8 as UTF8
-
-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.XMLConverter
-
-import Network.URI (parseRelativeReference, URI(uriPath))
-import qualified Data.Set as Set
-
---------------------------------------------------------------------------------
--- State
---------------------------------------------------------------------------------
-
-type Anchor = T.Text
-type Media = [(FilePath, B.ByteString)]
-
-data ReaderState
-   = ReaderState { -- | A collection of styles read somewhere else.
-                   -- It is only queried here, not modified.
-                   styleSet         :: Styles
-                   -- | A stack of the styles of parent elements.
-                   -- Used to look up inherited style properties.
-                 , styleTrace       :: [Style]
-                   -- | Keeps track of the current depth in nested lists
-                 , currentListLevel :: ListLevel
-                   -- | Keeps track of the previous list start counters,
-                   -- so whenever a new list continues numbering,
-                   -- we know what number to start from.
-                   -- If  list does not continue numbering, the counter
-                   -- is being reset.
-                 , listContinuationStartCounters :: M.Map ListLevel Int
-                   -- | Lists may provide their own style, but they don't have
-                   -- to. If they do not, the style of a parent list may be used
-                   -- or even a default list style from the paragraph style.
-                   -- This value keeps track of the closest list style there
-                   -- currently is.
-                 , currentListStyle :: Maybe ListStyle
-                   -- | A map from internal anchor names to "pretty" ones.
-                   -- The mapping is a purely cosmetic one.
-                 , bookmarkAnchors  :: M.Map Anchor Anchor
-                   -- | A map of files / binary data from the archive
-                 , envMedia         :: Media
-                   -- | Hold binary resources used in the document
-                 , odtMediaBag      :: MediaBag
-                 }
-  deriving ( Show )
-
-readerState :: Styles -> Media -> ReaderState
-readerState styles media = ReaderState styles [] 0 M.empty Nothing M.empty media mempty
-
---
-pushStyle'  :: Style -> ReaderState -> ReaderState
-pushStyle' style state = state { styleTrace = style : styleTrace state }
-
---
-popStyle'   :: ReaderState -> ReaderState
-popStyle' state = case styleTrace state of
-                   _:trace -> state  { styleTrace = trace  }
-                   _       -> state
---
-modifyListLevel :: (ListLevel -> ListLevel) -> (ReaderState -> ReaderState)
-modifyListLevel f state = state { currentListLevel = f (currentListLevel state) }
-
---
-modifyListContinuationStartCounter :: ListLevel -> Int -> (ReaderState -> ReaderState)
-modifyListContinuationStartCounter listLevel count state =
-    state { listContinuationStartCounters = M.insert listLevel count (listContinuationStartCounters state) }
-
---
-shiftListLevel :: ListLevel -> (ReaderState -> ReaderState)
-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
-
---
-putPrettyAnchor :: Anchor -> Anchor -> ReaderState -> ReaderState
-putPrettyAnchor ugly pretty state@ReaderState{..}
-  = state { bookmarkAnchors = M.insert ugly pretty bookmarkAnchors }
-
---
-usedAnchors :: ReaderState -> [Anchor]
-usedAnchors ReaderState{..} = M.elems bookmarkAnchors
-
-getMediaBag :: ReaderState -> MediaBag
-getMediaBag ReaderState{..} = odtMediaBag
-
-getMediaEnv :: ReaderState -> Media
-getMediaEnv ReaderState{..} = envMedia
-
-insertMedia' :: (FilePath, B.ByteString) -> ReaderState ->  ReaderState
-insertMedia' (fp, bs) state@ReaderState{..}
-  = state { odtMediaBag = insertMedia fp Nothing bs odtMediaBag }
-
---------------------------------------------------------------------------------
--- Reader type and associated tools
---------------------------------------------------------------------------------
-
-type ODTReader      a b = XMLReader     ReaderState a b
-
-type ODTReaderSafe  a b = XMLReaderSafe ReaderState a b
-
--- | Extract something from the styles
-fromStyles :: (a -> Styles -> b) -> ODTReaderSafe a b
-fromStyles f =     keepingTheValue
-                     (getExtraState >>^ styleSet)
-               >>% f
-
---
-getStyleByName :: ODTReader StyleName Style
-getStyleByName = fromStyles lookupStyle >>^ maybeToChoice
-
---
-findStyleFamily :: ODTReader Style StyleFamily
-findStyleFamily = fromStyles getStyleFamily >>^ maybeToChoice
-
---
-lookupListStyle :: ODTReader StyleName ListStyle
-lookupListStyle = fromStyles lookupListStyleByName >>^ maybeToChoice
-
---
-switchCurrentListStyle :: ODTReaderSafe (Maybe ListStyle) (Maybe ListStyle)
-switchCurrentListStyle =     keepingTheValue getExtraState
-                         >>% swapCurrentListStyle
-                         >>> first setExtraState
-                         >>^ snd
-
---
-pushStyle :: ODTReaderSafe Style Style
-pushStyle =     keepingTheValue (
-                  (     keepingTheValue getExtraState
-                    >>% pushStyle'
-                  )
-                  >>> setExtraState
-                )
-            >>^ fst
-
---
-popStyle :: ODTReaderSafe x x
-popStyle =     keepingTheValue (
-                     getExtraState
-                 >>> arr popStyle'
-                 >>> setExtraState
-               )
-           >>^ fst
-
---
-getCurrentListLevel :: ODTReaderSafe a ListLevel
-getCurrentListLevel = getExtraState >>^ currentListLevel
-
---
-getListContinuationStartCounters :: ODTReaderSafe a (M.Map ListLevel Int)
-getListContinuationStartCounters = getExtraState >>^ listContinuationStartCounters
-
-
---
-getPreviousListStartCounter :: ODTReaderSafe ListLevel Int
-getPreviousListStartCounter = proc listLevel -> do
-    counts <- getListContinuationStartCounters -< ()
-    returnA -< M.findWithDefault 0 listLevel counts
-
---
-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) -< ()
-
-type AnchorPrefix = T.Text
-
--- | An adaptation of 'uniqueIdent' from "Text.Pandoc.Shared" that generates a
--- unique identifier but without assuming that the id should be for a header.
--- Second argument is a list of already used identifiers.
-uniqueIdentFrom :: AnchorPrefix -> [Anchor] -> Anchor
-uniqueIdentFrom baseIdent usedIdents =
-  let  numIdent n = baseIdent <> "-" <> T.pack (show n)
-  in  if baseIdent `elem` usedIdents
-        then maybe baseIdent numIdent
-             $ find (\x -> numIdent x `notElem` usedIdents) ([1..60000] :: [Int])
-               -- if we have more than 60,000, allow repeats
-        else baseIdent
-
--- | 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 -< ()
-  case lookupPrettyAnchor uglyAnchor state of
-    Just prettyAnchor -> returnA -< prettyAnchor
-    Nothing           -> do
-      let newPretty = uniqueIdentFrom baseIdent (usedAnchors state)
-      modifyExtraState (putPrettyAnchor uglyAnchor newPretty) -<< newPretty
-
--- | Input: basis for a new header anchor
--- Output: saved new anchor
-getHeaderAnchor :: ODTReaderSafe Inlines Anchor
-getHeaderAnchor = proc 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
-
-
---------------------------------------------------------------------------------
--- 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)
-
---
-withNewStyle :: ODTReaderSafe x Inlines -> ODTReaderSafe x Inlines
-withNewStyle a = proc x -> do
-  fStyle <- 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
-  where
-    isCodeStyle :: StyleName -> Bool
-    isCodeStyle "Source_Text" = True
-    isCodeStyle "Source_20_Text" = True
-    isCodeStyle _             = False
-
-    inlineCode :: Inlines -> Inlines
-    inlineCode = code . T.concat . map stringify . toList
-
-type PropertyTriple = (ReaderState, TextProperties, Maybe StyleFamily)
-type InlineModifier = Inlines -> Inlines
-
--- | Given data about the local style changes, calculates how to modify
--- an instance of 'Inlines'
-modifierFromStyleDiff :: PropertyTriple -> InlineModifier
-modifierFromStyleDiff propertyTriple  =
-  composition $
-  getVPosModifier propertyTriple
-  : map (first ($ propertyTriple) >>> ifThen_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
-    getVPosModifier triple@(_,textProps,_) =
-        let getVPos = Just . verticalPosition
-        in  case lookupPreviousValueM getVPos triple of
-              Nothing      -> ignore
-              Just oldVPos -> getVPosModifier' (oldVPos, verticalPosition textProps)
-
-    getVPosModifier' (oldVPos , newVPos   ) | oldVPos == newVPos = ignore
-    getVPosModifier' ( _      , VPosSub   ) = subscript
-    getVPosModifier' ( _      , VPosSuper ) = superscript
-    getVPosModifier' ( _      ,  _        ) = ignore
-
-    hasEmphChanged :: PropertyTriple -> Bool
-    hasEmphChanged = swing any [ hasChanged  isEmphasised
-                               , hasChanged  underline
-                               ]
-
-    hasChanged property triple@(_, property -> newProperty, _) =
-        (/= Just newProperty) (lookupPreviousValue property triple)
-
-    lookupPreviousValue f = lookupPreviousStyleValue (fmap f . textProperties)
-
-    lookupPreviousValueM f = lookupPreviousStyleValue (f <=< textProperties)
-
-    lookupPreviousStyleValue f (ReaderState{..},_,mFamily)
-      =     findBy f (extendedStylePropertyChain styleTrace styleSet)
-        <|> (f . lookupDefaultStyle' styleSet =<< mFamily)
-
-
-type ParaModifier = Blocks -> Blocks
-
-_MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_MM_      :: Int
-_MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_PERCENT_ :: Int
-_MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_MM_      = 5
-_MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_PERCENT_ = 5
-
--- | Returns either 'id' or 'blockQuote' depending if any of the StyleProperties
--- are indented at quote level.
-getParaModifier :: ListLevel -> [StyleProperties] -> ParaModifier
-getParaModifier listLevel props
-  | listLevel > 0 = id -- see #9505, list paragraphs need indentation
-  | any isBlockQuote props = blockQuote
-  | otherwise = id
-  where
-  isBlockQuote SProps {..} | Just paraProps <- paraProperties
-                                    , isQuoteWidth (margin_left paraProps)
-                                    = True
-                                    | otherwise
-                                    = False
-  isQuoteWidth mMargin
-    | LengthValueMM margin <- mMargin
-    ,           margin > _MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_MM_
-     = True
-    | PercentValue  margin <- mMargin
-    ,           margin > _MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_PERCENT_
-     = True
-    | otherwise
-     = False
-
---
-constructPara :: ODTReaderSafe a Blocks -> ODTReaderSafe a Blocks
-constructPara reader = proc blocks -> do
-  fStyle <- readStyleByName -< blocks
-  case fStyle of
-    Left   _    -> reader -< blocks
-    Right (styleName, _) | isTableCaptionStyle styleName -> do
-      blocks' <- reader   -< blocks
-      arr tableCaptionP  -< blocks'
-    Right (_, style) -> do
-      props <- fromStyles extendedStylePropertyChain -< [style]
-      listLevel <- getCurrentListLevel -< ()
-      let modifier = getParaModifier listLevel props
-      blocks' <- reader   -<  blocks
-      arr modifier        -<< blocks'
-  where
-    isTableCaptionStyle :: StyleName -> Bool
-    isTableCaptionStyle "Table" = True
-    isTableCaptionStyle _       = False
-    tableCaptionP b = divWith ("", ["caption"], []) b
-
-type ListConstructor = [Blocks] -> Blocks
-
-getListConstructor :: ListLevelStyle -> Int -> ListConstructor
-getListConstructor ListLevelStyle{..} startNum =
-  case listLevelType of
-    LltBullet   -> bulletList
-    LltImage    -> bulletList
-    LltNumbered -> let listNumberStyle = toListNumberStyle listItemFormat
-                       listNumberDelim = toListNumberDelim listItemPrefix
-                                                           listItemSuffix
-                   in  orderedListWith (startNum, listNumberStyle, listNumberDelim)
-  where
-    toListNumberStyle  LinfNone      = DefaultStyle
-    toListNumberStyle  LinfNumber    = Decimal
-    toListNumberStyle  LinfRomanLC   = LowerRoman
-    toListNumberStyle  LinfRomanUC   = UpperRoman
-    toListNumberStyle  LinfAlphaLC   = LowerAlpha
-    toListNumberStyle  LinfAlphaUC   = UpperAlpha
-    toListNumberStyle (LinfString _) = Example
-
-    toListNumberDelim  Nothing   (Just ".") = Period
-    toListNumberDelim (Just "" ) (Just ".") = Period
-    toListNumberDelim  Nothing   (Just ")") = OneParen
-    toListNumberDelim (Just "" ) (Just ")") = OneParen
-    toListNumberDelim (Just "(") (Just ")") = TwoParens
-    toListNumberDelim     _          _      = DefaultDelim
-
--- | Determines which style to use for a list, which level to use of that
--- style, and which type of list to create as a result of this information.
--- 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
--- 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
-
-  let continueNumbering = case fContNumbering of
-                            Right "true" -> True
-                            _            -> False
-
-  let startNumForListLevelStyle = listStartingNumber continueNumbering listContinuationStartCounter
-  let defaultOrderedListConstructor = constructOrderedList (startNumForListLevelStyle Nothing) listLevel listItemCount
-
-  case fStyleName of
-    Right styleName -> do
-      fListStyle <- lookupListStyle -< styleName
-      case fListStyle of
-        Right listStyle -> do
-          fListLevelStyle <- arr (uncurry getListLevelStyle) -< (listLevel, listStyle)
-          case fListLevelStyle 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
-    Left _ -> do
-      state      <- getExtraState        -< ()
-      mListStyle <- arr currentListStyle -< state
-      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))
-
---------------------------------------------------------------------------------
--- Readers
---------------------------------------------------------------------------------
-
-type ElementMatcher result = (Namespace, ElementName, ODTReader result result)
-
-type InlineMatcher = ElementMatcher Inlines
-
-type BlockMatcher  = ElementMatcher Blocks
-
-newtype FirstMatch a = FirstMatch (Alt Maybe a)
-  deriving (Foldable, Monoid, Semigroup)
-
-firstMatch :: a -> FirstMatch a
-firstMatch = FirstMatch . Alt . Just
-
-newtype CombiningBlocks = CombiningBlocks { unCombiningBlocks :: Blocks }
-
-instance Semigroup CombiningBlocks where
-  CombiningBlocks l <> CombiningBlocks r = CombiningBlocks (combineBlocks l r)
-
-instance Monoid CombiningBlocks where
-  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
-
---
-matchChildContent    :: (Monoid result)
-                     => [ElementMatcher result]
-                     ->  ODTReaderSafe  (result, XML.Content) result
-                     ->  ODTReaderSafe a result
-matchChildContent ls fallback = returnV mempty >>> matchContent ls fallback
-
---------------------------------------------
--- Matchers
---------------------------------------------
-
-----------------------
--- Basics
-----------------------
-
---
--- | 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
-
-read_text_seq :: InlineMatcher
-read_text_seq  = matchingElement NsText "sequence"
-                 $ matchChildContent [] 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_line_break  :: InlineMatcher
-read_line_break   = matchingElement NsText "line-break"
-                    $ returnV linebreak
---
-read_tab         :: InlineMatcher
-read_tab          = matchingElement NsText "tab"
-                    $ returnV 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
-
---
-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
-
-
-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
-
-
-----------------------
--- Headers
-----------------------
-
---
-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
-  let idAttr = (anchor, [], []) -- no classes, no key-value pairs
-  arr (CombiningBlocks . uncurry3 headerWith) -< (idAttr, level, children)
-
-----------------------
--- Lists
-----------------------
-
---
-read_list        :: ElementMatcher CombiningBlocks
-read_list         = matchingElement NsText "list"
-                    $ liftA CombiningBlocks
-                    $ constructList
-                    $ matchChildContent' [ read_list_item
-                                         , read_list_header
-                                         ]
---
-read_list_item   :: ElementMatcher [Blocks]
-read_list_item    = read_list_element "list-item"
-
-read_list_header :: ElementMatcher [Blocks]
-read_list_header  = read_list_element "list-header"
-
-read_list_element               :: ElementName -> ElementMatcher [Blocks]
-read_list_element listElement   = matchingElement NsText listElement
-                                  $ liftA (compactify.(:[]))
-                                    ( matchSmushedChildBlocks' [ read_paragraph
-                                                               , read_header
-                                                               , read_list
-                                                               , read_section
-                                                               ]
-                                    )
-
-----------------------
--- Sections
-----------------------
-
-read_section :: ElementMatcher CombiningBlocks
-read_section = matchingElement NsText "section"
-                 $ liftA (CombiningBlocks . divWith nullAttr)
-                 $ matchSmushedChildBlocks' [ read_paragraph
-                                            , read_header
-                                            , read_list
-                                            , read_table
-                                            , read_section
-                                            ]
-
-
-----------------------
--- Links
-----------------------
-
-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                  )
-
-fixRelativeLink :: T.Text -> T.Text
-fixRelativeLink uri =
-    case parseRelativeReference (T.unpack uri) of
-      Nothing -> uri
-      Just u  ->
-        case uriPath u of
-          '.':'.':'/':xs -> tshow $ u{ uriPath = xs }
-          _ -> uri
-
--------------------------
--- Footnotes
--------------------------
-
-read_note        :: InlineMatcher
-read_note         = matchingElement NsText "note"
-                    $ liftA note
-                    $ matchChildContent' [ read_note_body ]
-
-read_note_body   :: BlockMatcher
-read_note_body    = matchingElement NsText "note-body"
-                    $ matchSmushedChildBlocks' [ read_paragraph ]
-
--------------------------
--- Citations
--------------------------
-
-read_citation    :: InlineMatcher
-read_citation     = matchingElement NsText "bibliography-mark"
-                    $ liftA2 cite
-                      ( liftA2 makeCitation
-                        ( findAttrTextWithDefault NsText "identifier" "" )
-                        ( readAttrWithDefault NsText "number" 0          )
-                      )
-                      ( matchChildContent [] read_plain_text             )
-  where
-   makeCitation :: T.Text -> Int -> [Citation]
-   makeCitation citeId num = [Citation citeId [] [] NormalCitation num 0]
-
-
-----------------------
--- Tables
-----------------------
-
---
-read_table        :: ElementMatcher CombiningBlocks
-read_table         = matchingElement NsTable "table"
-                     $ liftA (CombiningBlocks . table')
-                     $ (matchChildContent' [read_table_header]) &&&
-                       (matchChildContent' [read_table_row])
-
--- | A table without a caption.
-table' :: ([[Cell]], [[Cell]]) -> Blocks
-table' (headers, rows) =
-  table emptyCaption (replicate numcols defaults) th [tb] tf
-  where
-    defaults = (AlignDefault, ColWidthDefault)
-    numcols = maximum $ map length $ headers ++ rows
-    toRow = Row nullAttr
-    th = TableHead nullAttr $ map toRow headers
-    tb = TableBody nullAttr 0 [] $ map toRow rows
-    tf = TableFoot nullAttr []
-
---
-read_table_header :: ElementMatcher [[Cell]]
-read_table_header = matchingElement NsTable "table-header-rows"
-                      $ matchChildContent' [ read_table_row
-                                           ]
-
---
-read_table_row    :: ElementMatcher [[Cell]]
-read_table_row     = matchingElement NsTable "table-row"
-                     $ liftA (:[])
-                     $ matchChildContent'  [ read_table_cell
-                                           ]
-
---
-read_table_cell   :: ElementMatcher [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
-                                                ]
-  where
-    cell' rowSpan colSpan blocks = map (cell AlignDefault rowSpan colSpan) $ compactify [blocks]
-
-----------------------
--- Frames
-----------------------
-
---
-read_frame :: InlineMatcher
-read_frame = matchingElement NsDraw "frame"
-             $ filterChildrenName' NsDraw (`elem` ["image", "object", "text-box"])
-           >>> foldS read_frame_child
-           >>> arr fold
-
-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_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_title :: InlineMatcher
-read_frame_title = matchingElement NsSVG "title" (matchChildContent [] read_plain_text)
-
-image_attributes :: Maybe T.Text -> Maybe T.Text -> Attr
-image_attributes x y =
-  ( "", [], dim "width" x ++ dim "height" y)
-  where
-    dim _ (Just "")   = []
-    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_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_img_with_caption :: [Block] -> FirstMatch Inlines
-read_img_with_caption (Para [Image attr alt (src,title)] : _) =
-  firstMatch $ singleton (Image attr alt (src, "fig:" <> title))   -- no text, default caption
-read_img_with_caption (Para (Image attr _ (src,title) : txt) : _) =
-  firstMatch $ singleton (Image attr txt (src, "fig:" <> title) )  -- override caption with the text that follows
-read_img_with_caption  ( Para (_ : xs) : ys) =
-  read_img_with_caption (Para xs : ys)
-read_img_with_caption _ =
-  mempty
-
-----------------------
--- Internal links
-----------------------
-
-_ANCHOR_PREFIX_ :: T.Text
-_ANCHOR_PREFIX_ = "anchor"
-
---
-readAnchorAttr :: ODTReader a Anchor
-readAnchorAttr = findAttrText NsText "name"
-
--- | Beware: may fail
-findAnchorName :: ODTReader AnchorPrefix Anchor
-findAnchorName = (      keepingTheValue readAnchorAttr
-                   >>^  spreadChoice
-                 ) >>?! getPrettyAnchor
-
-
---
-maybeAddAnchorFrom :: ODTReader Inlines AnchorPrefix
-                   -> ODTReaderSafe Inlines Inlines
-maybeAddAnchorFrom anchorReader =
-  keepingTheValue (anchorReader >>? findAnchorName >>?^ toAnchorElem)
-  >>>
-  proc (inlines, fAnchorElem) -> do
-  case fAnchorElem of
-    Right anchorElem -> returnA -< anchorElem
-    Left _           -> returnA -< inlines
-  where
-    toAnchorElem :: Anchor -> Inlines
-    toAnchorElem anchorID = spanWith (anchorID, [], []) mempty
-                            -- no classes, no key-value pairs
-
---
-read_bookmark     :: InlineMatcher
-read_bookmark      = matchingElement NsText "bookmark"
-                     $ maybeAddAnchorFrom (liftAsSuccess $ returnV _ANCHOR_PREFIX_)
-
---
-read_bookmark_start :: InlineMatcher
-read_bookmark_start = matchingElement NsText "bookmark-start"
-                     $ maybeAddAnchorFrom (liftAsSuccess $ returnV _ANCHOR_PREFIX_)
-
---
-read_reference_start :: InlineMatcher
-read_reference_start = matchingElement NsText "reference-mark-start"
-                     $ maybeAddAnchorFrom readAnchorAttr
-
--- | Beware: may fail
-findAnchorRef :: ODTReader a Anchor
-findAnchorRef = (      findAttrText NsText "ref-name"
-                  >>?^ (_ANCHOR_PREFIX_,)
-                ) >>?! getPrettyAnchor
-
-
---
-maybeInAnchorRef :: ODTReaderSafe Inlines Inlines
-maybeInAnchorRef = proc inlines -> do
-  fRef <- findAnchorRef -< ()
-  case fRef of
-    Right anchor ->
-      arr (toAnchorRef anchor) -<< inlines
-    Left _ -> returnA -< inlines
-  where
-    toAnchorRef :: Anchor -> Inlines -> Inlines
-    toAnchorRef anchor = link ("#" <> anchor) "" -- no title
-
---
-read_bookmark_ref :: InlineMatcher
-read_bookmark_ref = matchingElement NsText "bookmark-ref"
-                    $    maybeInAnchorRef
-                     <<< matchChildContent [] read_plain_text
-
---
-read_reference_ref :: InlineMatcher
-read_reference_ref = matchingElement NsText "reference-ref"
-                    $    maybeInAnchorRef
-                     <<< matchChildContent [] read_plain_text
-
-
-----------------------
--- Entry point
-----------------------
-
-read_text :: ODTReaderSafe a Pandoc
-read_text = matchSmushedChildBlocks' [ read_header
-                                     , read_paragraph
-                                     , read_list
-                                     , read_section
-                                     , read_table
-                                     ]
-            >>^ doc
-
-post_process :: Pandoc -> Pandoc
-post_process (Pandoc m blocks) =
-  Pandoc m (post_process' blocks)
-
-post_process' :: [Block] -> [Block]
-post_process' (Table attr _ specs th tb tf : Div ("", ["caption"], _) blks : xs)
-  = Table attr (Caption Nothing blks) specs th tb tf : post_process' xs
-post_process' bs = bs
-
-read_body :: ODTReader a (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)
+{-# LANGUAGE DeriveFoldable    #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE PatternGuards     #-}
+{-# LANGUAGE RecordWildCards   #-}
+{-# LANGUAGE ViewPatterns      #-}
+{-# LANGUAGE OverloadedStrings #-}
+{- |
+   Module      : Text.Pandoc.Readers.ODT.ContentReader
+   Copyright   : Copyright (C) 2015 Martin Linnemann
+   License     : GNU GPL, version 2 or above
+
+   Maintainer  : Martin Linnemann <theCodingMarlin@googlemail.com>
+   Stability   : alpha
+   Portability : portable
+
+The core of the odt reader that converts odt features into Pandoc types.
+-}
+
+module Text.Pandoc.Readers.ODT.ContentReader
+( readerState
+, read_body
+) where
+
+import Control.Applicative ((<|>))
+import Control.Monad ((<=<))
+
+import Data.ByteString.Base64 (decodeLenient)
+import qualified Data.ByteString.Lazy as B
+import Data.Char (isSpace)
+import Data.Foldable (fold)
+import Data.List (find)
+import Data.List.NonEmpty (nonEmpty)
+import qualified Data.Map as M
+import qualified Data.Text as T
+import Data.Maybe
+import Data.Monoid (Alt (..))
+
+import Text.TeXMath (readMathML, writeTeX)
+import qualified Text.Pandoc.XML.Light as XML
+
+import Text.Pandoc.Builder hiding (underline)
+import Text.Pandoc.ImageSize (ImageType(..), imageType)
+import Text.Pandoc.MediaBag (MediaBag, insertMedia, mediaItems)
+import Text.Pandoc.MIME (extensionFromMimeType)
+import Text.Pandoc.Shared
+import Text.Pandoc.Walk (walk)
+import Text.Pandoc.Extensions (extensionsFromList, Extension(..))
+import qualified Text.Pandoc.UTF8 as UTF8
+
+import Text.Pandoc.Readers.Docx.Combine (combineBlocks)
+
+import Text.Pandoc.Readers.ODT.Namespaces
+import Text.Pandoc.Readers.ODT.StyleReader
+
+import Text.Pandoc.Readers.ODT.Generic.Utils (findBy)
+import Text.Pandoc.Readers.ODT.Generic.XMLConverter
+
+import Network.URI (parseRelativeReference, URI(uriPath))
+import qualified Data.Set as Set
+
+--------------------------------------------------------------------------------
+-- State
+--------------------------------------------------------------------------------
+
+type Anchor = T.Text
+type Media = M.Map FilePath B.ByteString
+
+data ReaderState
+   = ReaderState { -- | A collection of styles read somewhere else.
+                   -- It is only queried here, not modified.
+                   styleSet         :: Styles
+                   -- | A stack of the styles of parent elements.
+                   -- Used to look up inherited style properties.
+                 , styleTrace       :: [Style]
+                   -- | Keeps track of the current depth in nested lists
+                 , currentListLevel :: ListLevel
+                   -- | Keeps track of the previous list start counters,
+                   -- so whenever a new list continues numbering,
+                   -- we know what number to start from.
+                   -- If  list does not continue numbering, the counter
+                   -- is being reset.
+                 , listContinuationStartCounters :: M.Map ListLevel Int
+                   -- | Lists may provide their own style, but they don't have
+                   -- to. If they do not, the style of a parent list may be used
+                   -- or even a default list style from the paragraph style.
+                   -- This value keeps track of the closest list style there
+                   -- currently is.
+                 , currentListStyle :: Maybe ListStyle
+                   -- | A map from internal anchor names to "pretty" ones.
+                   -- The mapping is a purely cosmetic one.
+                 , bookmarkAnchors  :: M.Map Anchor Anchor
+                   -- | The "pretty" anchors handed out so far, i.e. the
+                   -- values of 'bookmarkAnchors'. Kept separately so that
+                   -- checking an anchor for uniqueness does not cost a
+                   -- traversal of the whole map.
+                 , usedAnchors      :: Set.Set Anchor
+                   -- | A map of files / binary data from the archive
+                 , envMedia         :: Media
+                   -- | Hold binary resources used in the document
+                 , odtMediaBag      :: MediaBag
+                 }
+  deriving ( Show )
+
+readerState :: Styles -> [(FilePath, B.ByteString)] -> ReaderState
+readerState styles media =
+  ReaderState styles [] 0 M.empty Nothing M.empty Set.empty
+              (M.fromList media) mempty
+
+--
+pushStyle'  :: Style -> ReaderState -> ReaderState
+pushStyle' style state = state { styleTrace = style : styleTrace state }
+
+--
+popStyle'   :: ReaderState -> ReaderState
+popStyle' state = case styleTrace state of
+                   _:trace -> state  { styleTrace = trace  }
+                   _       -> state
+--
+modifyListLevel :: (ListLevel -> ListLevel) -> (ReaderState -> ReaderState)
+modifyListLevel f state = state { currentListLevel = f (currentListLevel state) }
+
+--
+modifyListContinuationStartCounter :: ListLevel -> Int -> (ReaderState -> ReaderState)
+modifyListContinuationStartCounter listLevel count state =
+    state { listContinuationStartCounters = M.insert listLevel count (listContinuationStartCounters state) }
+
+--
+shiftListLevel :: ListLevel -> (ReaderState -> ReaderState)
+shiftListLevel diff = modifyListLevel (+ diff)
+
+--
+lookupPrettyAnchor :: Anchor -> ReaderState -> Maybe Anchor
+lookupPrettyAnchor anchor ReaderState{..} = M.lookup anchor bookmarkAnchors
+
+--
+putPrettyAnchor :: Anchor -> Anchor -> ReaderState -> ReaderState
+putPrettyAnchor ugly pretty state@ReaderState{..}
+  = state { bookmarkAnchors = M.insert ugly pretty bookmarkAnchors
+          , usedAnchors     = Set.insert pretty usedAnchors
+          }
+
+getMediaBag :: ReaderState -> MediaBag
+getMediaBag ReaderState{..} = odtMediaBag
+
+getMediaEnv :: ReaderState -> Media
+getMediaEnv ReaderState{..} = envMedia
+
+insertMedia' :: (FilePath, B.ByteString) -> ReaderState ->  ReaderState
+insertMedia' (fp, bs) state@ReaderState{..}
+  = state { odtMediaBag = insertMedia fp Nothing bs odtMediaBag }
+
+--------------------------------------------------------------------------------
+-- Reader type and associated tools
+--------------------------------------------------------------------------------
+
+type ODTReader a = XMLConverter Namespace ReaderState a
+
+-- | Extract the styles from the reader state
+getStyles :: ODTReader Styles
+getStyles = styleSet <$> getExtraState
+
+--
+getStyleByName :: StyleName -> ODTReader Style
+getStyleByName name = getStyles >>= fromMaybeF . lookupStyle name
+
+--
+--
+lookupListStyle :: StyleName -> ODTReader ListStyle
+lookupListStyle name = getStyles >>= fromMaybeF . lookupListStyleByName name
+
+--
+switchCurrentListStyle :: Maybe ListStyle -> ODTReader (Maybe ListStyle)
+switchCurrentListStyle mListStyle = do
+  state <- getExtraState
+  setExtraState $ state { currentListStyle = mListStyle }
+  return $ currentListStyle state
+
+--
+pushStyle :: Style -> ODTReader ()
+pushStyle style = modifyExtraState (pushStyle' style)
+
+--
+popStyle :: ODTReader ()
+popStyle = modifyExtraState popStyle'
+
+--
+getCurrentListLevel :: ODTReader ListLevel
+getCurrentListLevel = currentListLevel <$> getExtraState
+
+--
+getPreviousListStartCounter :: ListLevel -> ODTReader Int
+getPreviousListStartCounter listLevel =
+  M.findWithDefault 0 listLevel . listContinuationStartCounters
+    <$> getExtraState
+
+--
+updateMediaWithResource :: (FilePath, B.ByteString) -> ODTReader ()
+updateMediaWithResource resource = modifyExtraState (insertMedia' resource)
+
+--
+lookupResource :: FilePath -> ODTReader (Maybe B.ByteString)
+lookupResource target = M.lookup target . getMediaEnv <$> getExtraState
+
+type AnchorPrefix = T.Text
+
+-- | An adaptation of 'uniqueIdent' from "Text.Pandoc.Shared" that generates a
+-- unique identifier but without assuming that the id should be for a header.
+-- Second argument is a list of already used identifiers.
+uniqueIdentFrom :: AnchorPrefix -> Set.Set Anchor -> Anchor
+uniqueIdentFrom baseIdent usedIdents =
+  let  numIdent n = baseIdent <> "-" <> T.pack (show n)
+  in  if baseIdent `Set.member` usedIdents
+        then maybe baseIdent numIdent
+             $ find (\x -> numIdent x `Set.notMember` usedIdents)
+                 ([1..60000] :: [Int])
+               -- if we have more than 60,000, allow repeats
+        else baseIdent
+
+-- | 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 :: AnchorPrefix -> Anchor -> ODTReader Anchor
+getPrettyAnchor baseIdent uglyAnchor = do
+  state <- getExtraState
+  case lookupPrettyAnchor uglyAnchor state of
+    Just prettyAnchor -> return prettyAnchor
+    Nothing           -> do
+      let newPretty = uniqueIdentFrom baseIdent (usedAnchors state)
+      modifyExtraState (putPrettyAnchor uglyAnchor newPretty)
+      return newPretty
+
+-- | Input: basis for a new header anchor
+-- Output: saved new anchor
+getHeaderAnchor :: Inlines -> ODTReader Anchor
+getHeaderAnchor title = do
+  state <- getExtraState
+  let exts = extensionsFromList [Ext_auto_identifiers]
+  let anchor = uniqueIdent exts (toList title) (usedAnchors state)
+  modifyExtraState (putPrettyAnchor anchor anchor)
+  return anchor
+
+
+--------------------------------------------------------------------------------
+-- Working with styles
+--------------------------------------------------------------------------------
+
+-- | 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 :: ODTReader Inlines -> ODTReader Inlines
+withNewStyle reader = do
+  fStyle <- tryC readStyleByName
+  case fStyle of
+    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
+    isCodeStyle "Source_20_Text" = True
+    isCodeStyle _             = False
+
+    inlineCode :: Inlines -> Inlines
+    inlineCode = code . stringifyInlines
+
+type PropertyTriple = (ReaderState, TextProperties, Maybe StyleFamily)
+type InlineModifier = Inlines -> Inlines
+
+-- | Given data about the local style changes, calculates how to modify
+-- an instance of 'Inlines'
+modifierFromStyleDiff :: PropertyTriple -> InlineModifier
+modifierFromStyleDiff propertyTriple  =
+  foldr (.) id $
+  getVPosModifier propertyTriple
+  : map (\(hasChanged', modifier) ->
+           if hasChanged' propertyTriple then modifier else ignore)
+        [ (hasEmphChanged           , emph      )
+        , (hasChanged isStrong      , strong    )
+        , (hasChanged strikethrough , strikeout )
+        ]
+  where
+    ignore = id :: InlineModifier
+
+    getVPosModifier :: PropertyTriple -> InlineModifier
+    getVPosModifier triple@(_,textProps,_) =
+        let getVPos = Just . verticalPosition
+        in  case lookupPreviousValueM getVPos triple of
+              Nothing      -> ignore
+              Just oldVPos -> getVPosModifier' (oldVPos, verticalPosition textProps)
+
+    getVPosModifier' (oldVPos , newVPos   ) | oldVPos == newVPos = ignore
+    getVPosModifier' ( _      , VPosSub   ) = subscript
+    getVPosModifier' ( _      , VPosSuper ) = superscript
+    getVPosModifier' ( _      ,  _        ) = ignore
+
+    hasEmphChanged :: PropertyTriple -> Bool
+    hasEmphChanged triple = any ($ triple) [ hasChanged  isEmphasised
+                                           , hasChanged  underline
+                                           ]
+
+    hasChanged property triple@(_, property -> newProperty, _) =
+        (/= Just newProperty) (lookupPreviousValue property triple)
+
+    lookupPreviousValue f = lookupPreviousStyleValue (fmap f . textProperties)
+
+    lookupPreviousValueM f = lookupPreviousStyleValue (f <=< textProperties)
+
+    lookupPreviousStyleValue f (ReaderState{..},_,mFamily)
+      =     findBy f (extendedStylePropertyChain styleTrace styleSet)
+        <|> (f . lookupDefaultStyle' styleSet =<< mFamily)
+
+
+type ParaModifier = Blocks -> Blocks
+
+_MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_MM_      :: Int
+_MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_PERCENT_ :: Int
+_MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_MM_      = 5
+_MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_PERCENT_ = 5
+
+-- | Returns either 'id' or 'blockQuote' depending if any of the StyleProperties
+-- are indented at quote level.
+getParaModifier :: ListLevel -> [StyleProperties] -> ParaModifier
+getParaModifier listLevel props
+  | listLevel > 0 = id -- see #9505, list paragraphs need indentation
+  | any isBlockQuote props = blockQuote
+  | otherwise = id
+  where
+  isBlockQuote SProps {..} | Just paraProps <- paraProperties
+                                    , isQuoteWidth (margin_left paraProps)
+                                    = True
+                                    | otherwise
+                                    = False
+  isQuoteWidth mMargin
+    | LengthValueMM margin <- mMargin
+    ,           margin > _MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_MM_
+     = True
+    | PercentValue  margin <- mMargin
+    ,           margin > _MINIMUM_INDENTATION_FOR_BLOCKQUOTES_IN_PERCENT_
+     = True
+    | otherwise
+     = False
+
+--
+constructPara :: ODTReader Blocks -> ODTReader Blocks
+constructPara reader = do
+  fStyle <- tryC readStyleByName
+  case fStyle of
+    Left   _    -> reader
+    Right (styleName, _) | isTableCaptionStyle styleName ->
+      tableCaptionP <$> reader
+    Right (_, style) -> do
+      styles <- getStyles
+      let props = extendedStylePropertyChain [style] styles
+      listLevel <- getCurrentListLevel
+      getParaModifier listLevel props <$> reader
+  where
+    isTableCaptionStyle :: StyleName -> Bool
+    isTableCaptionStyle "Table" = True
+    isTableCaptionStyle _       = False
+    tableCaptionP b = divWith ("", ["caption"], []) b
+
+type ListConstructor = [Blocks] -> Blocks
+
+getListConstructor :: ListLevelStyle -> Int -> ListConstructor
+getListConstructor ListLevelStyle{..} startNum =
+  case listLevelType of
+    LltBullet   -> bulletList
+    LltImage    -> bulletList
+    LltNumbered -> let listNumberStyle = toListNumberStyle listItemFormat
+                       listNumberDelim = toListNumberDelim listItemPrefix
+                                                           listItemSuffix
+                   in  orderedListWith (startNum, listNumberStyle, listNumberDelim)
+  where
+    toListNumberStyle  LinfNone      = DefaultStyle
+    toListNumberStyle  LinfNumber    = Decimal
+    toListNumberStyle  LinfRomanLC   = LowerRoman
+    toListNumberStyle  LinfRomanUC   = UpperRoman
+    toListNumberStyle  LinfAlphaLC   = LowerAlpha
+    toListNumberStyle  LinfAlphaUC   = UpperAlpha
+    toListNumberStyle (LinfString _) = Example
+
+    toListNumberDelim  Nothing   (Just ".") = Period
+    toListNumberDelim (Just "" ) (Just ".") = Period
+    toListNumberDelim  Nothing   (Just ")") = OneParen
+    toListNumberDelim (Just "" ) (Just ")") = OneParen
+    toListNumberDelim (Just "(") (Just ")") = TwoParens
+    toListNumberDelim     _          _      = DefaultDelim
+
+-- | Determines which style to use for a list, which level to use of that
+-- style, and which type of list to create as a result of this information.
+-- 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. 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 :: 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 = fContNumbering == Right "true"
+
+      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 <- tryC (lookupListStyle styleName)
+      case fListStyle of
+        Right listStyle ->
+          case getListLevelStyle listLevel listStyle of
+            Just listLevelStyle -> do
+              oldListStyle <- switchCurrentListStyle (Just listStyle)
+              blocks       <- constructListWith listLevelStyle
+              _            <- switchCurrentListStyle oldListStyle
+              return blocks
+            Nothing             -> constructOrderedList
+        Left _                  -> constructOrderedList
+    Left _ -> do
+      mListStyle <- currentListStyle <$> getExtraState
+      case mListStyle of
+        Just listStyle ->
+          case getListLevelStyle listLevel listStyle of
+            Just listLevelStyle -> constructListWith listLevelStyle
+            Nothing             -> constructOrderedList
+        Nothing                 -> constructOrderedList
+
+--------------------------------------------------------------------------------
+-- Readers
+--------------------------------------------------------------------------------
+
+type Matcher result = ElementMatcher Namespace ReaderState result
+
+type InlineMatcher = Matcher Inlines
+
+type BlockMatcher  = Matcher Blocks
+
+newtype FirstMatch a = FirstMatch (Alt Maybe a)
+  deriving (Foldable, Monoid, Semigroup)
+
+firstMatch :: a -> FirstMatch a
+firstMatch = FirstMatch . Alt . Just
+
+newtype CombiningBlocks = CombiningBlocks { unCombiningBlocks :: Blocks }
+
+instance Semigroup CombiningBlocks where
+  CombiningBlocks l <> CombiningBlocks r = CombiningBlocks (combineBlocks l r)
+
+instance Monoid CombiningBlocks where
+  mempty = CombiningBlocks mempty
+
+--
+matchingElement :: Namespace -> ElementName
+                -> ODTReader e
+                -> Matcher e
+matchingElement ns name reader = (ns, name, reader)
+
+--
+matchSmushedChildBlocks' :: [Matcher CombiningBlocks] -> ODTReader Blocks
+matchSmushedChildBlocks' ls = unCombiningBlocks <$> matchContent' ls
+
+--------------------------------------------
+-- Matchers
+--------------------------------------------
+
+----------------------
+-- Basics
+----------------------
+
+--
+-- | 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"
+                 $ 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.
+-- The repeat count is attacker-controlled and unbounded in ODF, so cap
+-- it: a 832-byte document with @text:c="200000000"@ would otherwise
+-- exhaust memory.
+_MAX_SPACES_     :: Int
+_MAX_SPACES_      = 1000
+
+read_spaces      :: InlineMatcher
+read_spaces       = matchingElement NsText "s" $ do
+                      count <- readAttrWithDefault NsText "c" 1 -- how many spaces?
+                      return $ fromList (replicate (min count _MAX_SPACES_) Space)
+--
+read_line_break  :: InlineMatcher
+read_line_break   = matchingElement NsText "line-break"
+                    $ return linebreak
+--
+read_tab         :: InlineMatcher
+read_tab          = matchingElement NsText "tab"
+                    $ return space
+--
+read_span        :: InlineMatcher
+read_span         = matchingElement NsText "span"
+                    $ withNewStyle matchInlineContent
+
+-- | ODF wraps inline content in a few elements that carry nothing but
+-- metadata: @text:meta@ holds RDFa annotations, @text:meta-field@ marks
+-- up bibliographic data. They are transparent as far as we are concerned.
+read_meta        :: InlineMatcher
+read_meta         = matchingElement NsText "meta" matchInlineContent
+
+read_meta_field  :: InlineMatcher
+read_meta_field   = matchingElement NsText "meta-field" matchInlineContent
+
+-- | A ruby annotation glosses a base text. Pandoc cannot represent the
+-- gloss, so keep the base text rather than dropping both.
+read_ruby        :: InlineMatcher
+read_ruby         = matchingElement NsText "ruby"
+                    $ matchContent' [ matchingElement NsText "ruby-base"
+                                        matchInlineContent ]
+
+-- | Fields hold a value that the producing application computes, such as
+-- a page number, a document property or a user variable. The element
+-- content is the text that application last displayed for the field,
+-- which is the best rendering available to us.
+read_fields      :: [InlineMatcher]
+read_fields       = [ matchingElement NsText name (matchContent [] read_plain_text)
+                    | name <- fieldElements ]
+
+fieldElements    :: [ElementName]
+fieldElements     =
+  [ "page-number", "page-count", "page-continuation", "page-variable-get"
+  , "date", "time", "creation-date", "creation-time"
+  , "modification-date", "modification-time"
+  , "print-date", "print-time", "printed-by"
+  , "editing-cycles", "editing-duration"
+  , "author-name", "author-initials", "initial-creator", "creator"
+  , "title", "subject", "description", "keywords"
+  , "chapter", "file-name", "template-name", "sheet-name"
+  , "variable-get", "variable-set", "variable-input"
+  , "user-defined", "user-field-get", "user-field-input"
+  , "expression", "text-input", "placeholder"
+  , "word-count", "character-count", "paragraph-count"
+  , "table-count", "image-count", "object-count"
+  , "database-display", "database-name", "database-row-number"
+  , "sequence-ref", "note-ref"
+  , "sender-firstname", "sender-lastname", "sender-initials"
+  , "sender-title", "sender-position", "sender-email"
+  , "sender-company", "sender-street", "sender-city"
+  , "sender-postal-code", "sender-country", "sender-state-or-province"
+  , "sender-phone-private", "sender-phone-work", "sender-fax"
+  ]
+
+--
+read_paragraph   :: Matcher CombiningBlocks
+read_paragraph    = matchingElement NsText "p" $ fmap CombiningBlocks $ do
+                      fStyle <- tryC readStyleByName
+                      case fStyle of
+                        Right style | isPreformattedStyle style ->
+                          codeBlock . stringifyInlines <$> matchInlineContent
+                        _ ->
+                          constructPara (para <$> withNewStyle matchInlineContent)
+                    where
+                      isPreformattedStyle :: (StyleName, Style) -> Bool
+                      isPreformattedStyle ("Preformatted_20_Text", _) = True
+                      isPreformattedStyle (_, Style { styleParentName = Just "Preformatted_20_Text" }) = True
+                      isPreformattedStyle _ = False
+
+
+-- | Reads the inline content of a paragraph, heading or span.
+matchInlineContent :: ODTReader Inlines
+matchInlineContent = matchContent inlineMatchers read_plain_text
+
+inlineMatchers :: [InlineMatcher]
+inlineMatchers = [ 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_meta
+                 , read_meta_field
+                 , read_ruby
+                 ] ++ read_fields
+
+-- | Reads the block-level content that ODF permits wherever it permits a
+-- paragraph: in the body of the document, and in a section, a list item, a
+-- footnote or a table cell.
+matchBlockContent :: ODTReader Blocks
+matchBlockContent = matchSmushedChildBlocks' blockMatchers
+
+blockMatchers :: [Matcher CombiningBlocks]
+blockMatchers = [ read_paragraph
+                , read_header
+                , read_list
+                , read_table
+                , read_section
+                ]
+
+
+----------------------
+-- Headers
+----------------------
+
+--
+read_header      :: Matcher CombiningBlocks
+read_header       = matchingElement NsText "h" $ do
+  level    <- readAttrWithDefault NsText "outline-level" 1
+  children <- matchInlineContent
+  anchor   <- getHeaderAnchor children
+  let idAttr = (anchor, [], []) -- no classes, no key-value pairs
+  return $ CombiningBlocks $ headerWith idAttr level children
+
+----------------------
+-- Lists
+----------------------
+
+--
+read_list        :: Matcher CombiningBlocks
+read_list         = matchingElement NsText "list"
+                    $ CombiningBlocks
+                    <$> constructList
+                        ( matchContent' [ read_list_item
+                                        , read_list_header
+                                        ] )
+--
+read_list_item   :: Matcher [Blocks]
+read_list_item    = read_list_element "list-item"
+
+read_list_header :: Matcher [Blocks]
+read_list_header  = read_list_element "list-header"
+
+read_list_element               :: ElementName -> Matcher [Blocks]
+read_list_element listElement   = matchingElement NsText listElement
+                                  $ compactify . (:[])
+                                  <$> matchBlockContent
+
+----------------------
+-- Sections
+----------------------
+
+read_section :: Matcher CombiningBlocks
+read_section = matchingElement NsText "section"
+                 $ CombiningBlocks . divWith nullAttr
+                 <$> matchBlockContent
+
+
+----------------------
+-- Links
+----------------------
+
+read_link        :: InlineMatcher
+read_link         = matchingElement NsText "a"
+                    $ 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 =
+    case parseRelativeReference (T.unpack uri) of
+      Nothing -> uri
+      Just u  ->
+        case uriPath u of
+          '.':'.':'/':xs -> tshow $ u{ uriPath = xs }
+          _ -> uri
+
+-------------------------
+-- Footnotes
+-------------------------
+
+read_note        :: InlineMatcher
+read_note         = matchingElement NsText "note"
+                    $ note <$> matchContent' [ read_note_body ]
+
+read_note_body   :: BlockMatcher
+read_note_body    = matchingElement NsText "note-body" matchBlockContent
+
+-------------------------
+-- Citations
+-------------------------
+
+read_citation    :: InlineMatcher
+read_citation     = matchingElement NsText "bibliography-mark"
+                    $ 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]
+
+
+----------------------
+-- Tables
+----------------------
+
+--
+read_table        :: Matcher CombiningBlocks
+read_table         = matchingElement NsTable "table"
+                     $ fmap (CombiningBlocks . table')
+                     $ (,) <$> matchContent' read_header_rows
+                           <*> matchContent' read_body_rows
+
+-- | A table without a caption.
+table' :: ([[Cell]], [[Cell]]) -> Blocks
+table' (headers, rows) = compactifyTable $
+    table emptyCaption (replicate numcols defaults) th [tb] tf
+  where
+    defaults = (AlignDefault, ColWidthDefault)
+    -- A table element need not contain any rows, so guard against
+    -- taking the maximum of an empty list.
+    numcols = maybe 0 maximum $ nonEmpty $ map rowWidth $ headers ++ rows
+    -- A cell spanning several columns occupies all of them.
+    rowWidth = sum . map (\(Cell _ _ _ (ColSpan cs) _) -> cs)
+    toRow = Row nullAttr
+    th = TableHead nullAttr $ map toRow headers
+    tb = TableBody nullAttr 0 [] $ map toRow rows
+    tf = TableFoot nullAttr []
+
+-- | Rows need not be immediate children of the table: ODF lets them be
+-- wrapped in any number of @table:table-row-group@ and @table:table-rows@
+-- elements, which only group them for the benefit of outlining and
+-- formatting. The children are walked twice, once picking up the rows that a
+-- @table:table-header-rows@ marks as headers and once the remaining ones.
+read_header_rows  :: [Matcher [[Cell]]]
+read_header_rows   = [ matchingElement NsTable "table-header-rows"
+                         $ matchContent' [ read_table_row ]
+                     , matchingElement NsTable "table-row-group"
+                         $ matchContent' read_header_rows
+                     ]
+
+read_body_rows    :: [Matcher [[Cell]]]
+read_body_rows     = [ read_table_row
+                     , matchingElement NsTable "table-rows"
+                         $ matchContent' [ read_table_row ]
+                     , matchingElement NsTable "table-row-group"
+                         $ matchContent' read_body_rows
+                     ]
+
+-- | ODF abbreviates a run of identical rows or cells with a repeat count.
+-- The counts are unbounded in the format -- spreadsheets pad out to the end
+-- of the sheet with them -- so a handful of bytes could otherwise ask for an
+-- arbitrarily large table. Bound both the width a single cell element stands
+-- for and the number of cells a single row element expands to; capping the
+-- two counts separately would not do, as they multiply.
+_MAX_COLUMNS_, _MAX_ROW_CELLS_ :: Int
+_MAX_COLUMNS_      = 1000
+_MAX_ROW_CELLS_    = 10000
+
+repeated          :: ElementName -> ODTReader Int
+repeated attrName  = max 1 <$> readAttrWithDefault NsTable attrName 1
+
+--
+read_table_row    :: Matcher [[Cell]]
+read_table_row     = matchingElement NsTable "table-row"
+                     $ row'
+                     <$> matchContent' [ read_table_cell
+                                       ]
+                     <*> repeated "number-rows-repeated"
+  where
+    row' cells repeat' =
+      replicate (max 1 $ min repeat'
+                       $ _MAX_ROW_CELLS_ `div` max 1 (length cells)) cells
+
+--
+read_table_cell   :: Matcher [Cell]
+read_table_cell    = matchingElement NsTable "table-cell"
+                     $ cell'
+                       <$> (RowSpan <$> readAttrWithDefault NsTable "number-rows-spanned" 1)
+                       <*> (ColSpan <$> readAttrWithDefault NsTable "number-columns-spanned" 1)
+                       <*> (min _MAX_COLUMNS_ <$> repeated "number-columns-repeated")
+                       <*> matchBlockContent
+  where
+    cell' rowSpan colSpan repeat' blocks =
+      concat $ replicate repeat'
+             $ map (cell AlignDefault rowSpan colSpan) (compactify [blocks])
+
+----------------------
+-- Frames
+----------------------
+
+--
+read_frame :: InlineMatcher
+read_frame = matchingElement NsDraw "frame" $ do
+  children <- filterChildrenName' NsDraw (`elem` ["image", "object", "text-box"])
+  fold . mconcat <$> mapM read_frame_child children
+
+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 :: XML.Element -> ODTReader (FirstMatch Inlines)
+read_frame_img img = do
+  mbSrc <- executeIn img resolveImageSrc
+  case mbSrc of
+    Nothing  -> return mempty
+    Just src -> do
+      -- note: the remaining attributes are read in the context of the
+      -- enclosing draw:frame, not of the draw:image
+      let exts = extensionsFromList [Ext_auto_identifiers]
+      w          <- findAttr' NsSVG "width"
+      h          <- findAttr' NsSVG "height"
+      titleNodes <- matchContent' [ read_frame_title ]
+      -- trimmed because a pretty-printed file indents the children of
+      -- the frame, and that whitespace is not alt text
+      alt        <- trimInlines <$> matchContent [] read_plain_text
+      return $ firstMatch
+             $ imageWith (image_attributes w h) src
+                         (inlineListToIdentifier exts (toList titleNodes))
+                         alt
+
+-- | Determine the media bag path of the image of the current
+-- @draw:image@ element.  The image data is either in another file of the
+-- archive, referenced by @xlink:href@, or embedded in the element as
+-- base64 in an @office:binary-data@ child (which is how a flat
+-- OpenDocument file must carry it).
+resolveImageSrc :: ODTReader (Maybe T.Text)
+resolveImageSrc = do
+  href <- fold <$> findAttr' NsXLink "href"
+  if not (T.null href)
+     then do
+       let src = fixRelativeLink href
+       -- The archive need not actually contain the referenced file; if it
+       -- does not, leave the media bag alone and just emit the link.
+       mapM_ (updateMediaWithResource . (,) (T.unpack src))
+         =<< lookupResource (T.unpack src)
+       return (Just src)
+     else do
+       drawImage <- getCurrentElement
+       findChild' NsOffice "binary-data"
+         >>= traverse (embedBase64Image drawImage)
+
+-- | Add the base64-encoded image data of an @office:binary-data@ element
+-- to the media bag and return the path under which it was filed.  The
+-- mime type is declared on the enclosing @draw:image@.
+embedBase64Image :: XML.Element -> XML.Element -> ODTReader T.Text
+embedBase64Image drawImage binaryData = do
+  let bytes = decodeLenient . UTF8.fromText
+            . T.filter (not . isSpace) . XML.strContent $ binaryData
+  -- the attribute is draw:mime-type in ODF 1.3 but loext:mime-type in
+  -- older LibreOffice output, so match on the local name only
+  let mbExtension =
+        ((("." <>) <$>) . extensionFromMimeType
+          =<< XML.findAttrBy ((== "mime-type") . XML.qName) drawImage)
+        <|> (extensionForImageType <$> imageType bytes)
+  n <- length . mediaItems . getMediaBag <$> getExtraState
+  let path = "Pictures/image" <> show (n + 1) <> T.unpack (fromMaybe ".bin" mbExtension)
+  updateMediaWithResource (path, B.fromStrict bytes)
+  return (T.pack path)
+
+extensionForImageType :: ImageType -> T.Text
+extensionForImageType imgType =
+  case imgType of
+    Png  -> ".png"
+    Jpeg -> ".jpeg"
+    Gif  -> ".gif"
+    Pdf  -> ".pdf"
+    Eps  -> ".eps"
+    Svg  -> ".svg"
+    Emf  -> ".emf"
+    Tiff -> ".tiff"
+    Webp -> ".webp"
+    Avif -> ".avif"
+
+read_frame_title :: InlineMatcher
+read_frame_title = matchingElement NsSVG "title" (matchContent [] read_plain_text)
+
+image_attributes :: Maybe T.Text -> Maybe T.Text -> Attr
+image_attributes x y =
+  ( "", [], dim "width" x ++ dim "height" y)
+  where
+    dim _ (Just "")   = []
+    dim name (Just v) = [(name, v)]
+    dim _ Nothing     = []
+
+read_frame_mathml :: XML.Element -> ODTReader (FirstMatch Inlines)
+read_frame_mathml obj = do
+  href <- fold <$> executeIn obj (findAttr' NsXLink "href")
+  mbMathML <-
+    if T.null href
+       then -- a flat OpenDocument file has to embed the formula in the
+            -- draw:object itself
+            return $ XML.showElement . addMathMLNamespace <$> findMathML obj
+       else do
+         -- note that pandoc's own odt writer uses a trailing slash
+         let dir = T.dropWhileEnd (== '/') $
+                     fromMaybe href (T.stripPrefix "./" href)
+         fmap (UTF8.toText . B.toStrict) <$>
+           lookupResource (T.unpack (dir <> "/content.xml"))
+  case readMathML <$> mbMathML of
+    Just (Right exps) -> return $ firstMatch $ displayMath $ writeTeX exps
+    _                 -> return mempty
+
+-- | Find the MathML of a formula embedded in a @draw:object@.  It is
+-- searched for as a descendant, not as a direct child, because the
+-- @math@ element may be wrapped in an @office:document@ element
+-- describing an embedded formula document.
+findMathML :: XML.Element -> Maybe XML.Element
+findMathML = XML.filterElementName ((== "math") . XML.qName)
+
+-- | XML.Light carries namespace prefixes literally and does not
+-- synthesise declarations, so the declaration of the MathML namespace
+-- may have been left behind on the root of the flat file.
+addMathMLNamespace :: XML.Element -> XML.Element
+addMathMLNamespace el =
+  el{ XML.elAttribs =
+        XML.Attr (XML.QName "xmlns" Nothing Nothing)
+                 "http://www.w3.org/1998/Math/MathML"
+        : filter (not . isDefaultNS . XML.attrKey) (XML.elAttribs el) }
+ where
+  isDefaultNS qn = XML.qName qn == "xmlns" && isNothing (XML.qPrefix qn)
+
+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)] : _) =
+  firstMatch $ singleton (Image attr alt (src, "fig:" <> title))   -- no text, default caption
+read_img_with_caption (Para (Image attr _ (src,title) : txt) : _) =
+  firstMatch $ singleton (Image attr txt (src, "fig:" <> title) )  -- override caption with the text that follows
+read_img_with_caption  ( Para (_ : xs) : ys) =
+  read_img_with_caption (Para xs : ys)
+read_img_with_caption _ =
+  mempty
+
+----------------------
+-- Internal links
+----------------------
+
+_ANCHOR_PREFIX_ :: T.Text
+_ANCHOR_PREFIX_ = "anchor"
+
+--
+readAnchorAttr :: ODTReader Anchor
+readAnchorAttr = findAttr NsText "name"
+
+-- | Beware: may fail
+findAnchorName :: AnchorPrefix -> ODTReader Anchor
+findAnchorName anchorPrefix = do
+  uglyAnchor <- readAnchorAttr
+  getPrettyAnchor anchorPrefix uglyAnchor
+
+--
+maybeAddAnchorFrom :: ODTReader AnchorPrefix -> ODTReader Inlines
+maybeAddAnchorFrom anchorReader =
+      (toAnchorElem <$> (anchorReader >>= findAnchorName))
+  <|> return mempty
+  where
+    toAnchorElem :: Anchor -> Inlines
+    toAnchorElem anchorID = spanWith (anchorID, [], []) mempty
+                            -- no classes, no key-value pairs
+
+--
+read_bookmark     :: InlineMatcher
+read_bookmark      = matchingElement NsText "bookmark"
+                     $ maybeAddAnchorFrom (return _ANCHOR_PREFIX_)
+
+--
+read_bookmark_start :: InlineMatcher
+read_bookmark_start = matchingElement NsText "bookmark-start"
+                     $ maybeAddAnchorFrom (return _ANCHOR_PREFIX_)
+
+--
+read_reference_start :: InlineMatcher
+read_reference_start = matchingElement NsText "reference-mark-start"
+                     $ maybeAddAnchorFrom readAnchorAttr
+
+-- | Beware: may fail
+findAnchorRef :: ODTReader Anchor
+findAnchorRef = do
+  uglyAnchor <- findAttr NsText "ref-name"
+  getPrettyAnchor _ANCHOR_PREFIX_ uglyAnchor
+
+--
+maybeInAnchorRef :: Inlines -> ODTReader Inlines
+maybeInAnchorRef inlines = do
+  fRef <- tryC findAnchorRef
+  case fRef of
+    Right anchor -> return $ toAnchorRef anchor inlines
+    Left _       -> return inlines
+  where
+    toAnchorRef :: Anchor -> Inlines -> Inlines
+    toAnchorRef anchor = link ("#" <> anchor) "" -- no title
+
+--
+read_bookmark_ref :: InlineMatcher
+read_bookmark_ref = matchingElement NsText "bookmark-ref"
+                    $ matchContent [] read_plain_text >>= maybeInAnchorRef
+
+--
+read_reference_ref :: InlineMatcher
+read_reference_ref = matchingElement NsText "reference-ref"
+                    $ matchContent [] read_plain_text >>= maybeInAnchorRef
+
+
+----------------------
+-- Entry point
+----------------------
+
+read_text :: ODTReader Pandoc
+read_text = doc <$> matchBlockContent
+
+post_process :: Pandoc -> Pandoc
+post_process = walk (unwrapCaptions . attachCaptions)
+
+-- | Paragraphs styled as table captions are read as a Div with class
+-- @caption@ (see 'constructPara'). Attach each of them to the adjacent
+-- table; ODF puts the caption either before or after its table.
+attachCaptions :: [Block] -> [Block]
+attachCaptions (Table attr _ specs th tb tf : b : xs)
+  | Just blks <- captionBlocks b
+  = Table attr (Caption Nothing blks) specs th tb tf : attachCaptions xs
+attachCaptions (b : Table attr _ specs th tb tf : xs)
+  | Just blks <- captionBlocks b
+  = Table attr (Caption Nothing blks) specs th tb tf : attachCaptions xs
+attachCaptions (b : bs) = b : attachCaptions bs
+attachCaptions []       = []
+
+-- | The Div is only an internal marker, so a caption paragraph that
+-- turned out not to belong to a table is emitted as an ordinary
+-- paragraph rather than leaking the marker into the output.
+unwrapCaptions :: [Block] -> [Block]
+unwrapCaptions = concatMap (\b -> fromMaybe [b] (captionBlocks b))
+
+captionBlocks :: Block -> Maybe [Block]
+captionBlocks (Div ("", ["caption"], _) blks) = Just blks
+captionBlocks _                               = Nothing
+
+read_body :: ODTReader (Pandoc, MediaBag)
+read_body = executeInSub NsOffice "body"
+          $ executeInSub NsOffice "text"
+          $ 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,34 @@
    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
+, getCurrentElement
+, elName
 , findChild'
 , filterChildrenName'
 , isSet'
 , isSetWithDefault
-, elName
 , searchAttr
 , lookupAttr
 , lookupAttr'
 , lookupDefaultingAttr
 , findAttr'
-, findAttrText'
 , findAttr
-, findAttrText
-, findAttrTextWithDefault
+, findAttrWithDefault
 , readAttr
 , readAttr'
 , readAttrWithDefault
@@ -49,271 +44,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 +202,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 +234,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 +483,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,13 +37,10 @@
 , 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
-import qualified Data.List as L
 import qualified Data.Map as M
 import Data.Maybe
 import Data.Text (Text)
@@ -56,18 +51,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 +91,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 +121,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 +393,78 @@
 --------------------------------------------------------------------------------
 
 --
-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)]
+        -- As in CSS, `bold` is weight 700; lighter weights are not bold.
         isFontBold = ("normal",False):("bold",True)
-                    :map ((,True) . tshow) ([100,200..900]::[Int])
+                    :map (\w -> (tshow w, w >= 700)) ([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)
 
 --
@@ -602,17 +567,27 @@
 -- | Returns a chain of parent of the current style. The direct parent will
 -- be the first element of the list, followed by its parent and so on.
 -- The current style is not in the list.
+-- Note that @style:parent-style-name@ references can be cyclic, so we
+-- keep track of the names already seen to avoid looping forever.
 parents               :: Style       -> Styles ->      [Style]
-parents style styles = L.unfoldr findNextParent style -- Ha!
-  where findNextParent Style{..}
-          = fmap duplicate $ (`lookupStyle` styles) =<< styleParentName
+parents style styles = go S.empty style
+  where
+    go seen Style{..}
+      | Just name <- styleParentName
+      , not (name `S.member` seen)
+      , Just parent <- lookupStyle name styles
+      = parent : go (S.insert name seen) parent
+      | otherwise
+      = []
 
 -- | Looks up the style family of the current style. Normally, every style
 -- should have one. But if not, all parents are searched.
+-- Searching the parents recursively would repeatedly walk the same
+-- chains, taking time exponential in its length; the chain is linear,
+-- so we can just walk it once.
 getStyleFamily        :: Style       -> Styles -> Maybe StyleFamily
-getStyleFamily style@Style{..} styles
-  =     styleFamily
-    <|> F.asum (map (`getStyleFamily` styles) $ parents style styles)
+getStyleFamily style styles
+  = F.asum $ map styleFamily $ style : parents style styles
 
 -- | Each 'Style' has certain 'StyleProperties'. But sometimes not all property
 -- values are specified. Instead, a value might be inherited from a
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
@@ -34,8 +34,9 @@
 import Safe (lastMay)
 import Control.Monad (guard, mplus, mzero, unless, when, void)
 import Control.Monad.Trans (lift)
-import Data.Char (isAlphaNum, isSpace)
+import Data.Char (isAlpha, isAlphaNum, isSpace)
 import qualified Data.Map as M
+import qualified Data.Set as Set
 import Data.Text (Text)
 import qualified Data.Text as T
 
@@ -78,8 +79,27 @@
 
 -- | Parse a single Org-mode inline element
 inline :: PandocMonad m => OrgParser m (F Inlines)
-inline =
-  choice [ whitespace
+inline = do
+  c <- lookAhead anyChar
+  (if isStrEnd c then inlineMarkup else inlineWord c)
+    <* (guard =<< newlinesCountWithinLimits)
+    <?> "inline"
+ where
+  -- Fast path for a character that starts a plain word.  Every
+  -- alternative in 'inlineMarkup' other than those below begins with a
+  -- character for which 'isStrEnd' holds, so none of them can match
+  -- here; and 'str' always succeeds, so nothing after it is reachable.
+  -- The remaining candidates are 'plainLink' (whose URI schemes all
+  -- start with a letter), the org-ref form of 'cite' (which must start
+  -- with "cite") and 'inlineCodeBlock' (which must start with "src_").
+  inlineWord c = case c of
+    'c' -> cite <|> plainLink <|> str
+    's' -> plainLink <|> inlineCodeBlock <|> str
+    _ | isAlpha c -> plainLink <|> str
+      | otherwise -> str
+
+  inlineMarkup = choice
+         [ whitespace
          , linebreak
          , cite
          , footnote
@@ -101,8 +121,7 @@
          , smartQuotes
          , specialStrings
          , symbol
-         ] <* (guard =<< newlinesCountWithinLimits)
-  <?> "inline"
+         ]
 
 -- | Read the rest of the input as inlines.
 inlines :: PandocMonad m => OrgParser m (F Inlines)
@@ -112,7 +131,44 @@
 specialChars :: [Char]
 specialChars = "\"$'()*+-,./:;<=>@[\\]^_{|}~"
 
+-- | True for characters that terminate a plain 'str' run, i.e. the
+-- 'specialChars' plus whitespace and newlines.  Written as a @case@ so
+-- that GHC compiles it to a jump table instead of a linear scan over
+-- 'specialChars'; this predicate is applied to every input character.
+isStrEnd :: Char -> Bool
+isStrEnd c = case c of
+  ' '  -> True
+  '\n' -> True
+  '\r' -> True
+  '"'  -> True
+  '$'  -> True
+  '\'' -> True
+  '('  -> True
+  ')'  -> True
+  '*'  -> True
+  '+'  -> True
+  '-'  -> True
+  ','  -> True
+  '.'  -> True
+  '/'  -> True
+  ':'  -> True
+  ';'  -> True
+  '<'  -> True
+  '='  -> True
+  '>'  -> True
+  '@'  -> True
+  '['  -> True
+  '\\' -> True
+  ']'  -> True
+  '^'  -> True
+  '_'  -> True
+  '{'  -> True
+  '|'  -> True
+  '}'  -> True
+  '~'  -> True
+  _    -> False
 
+
 whitespace :: PandocMonad m => OrgParser m (F Inlines)
 whitespace = pure B.space <$ skipMany1 spaceChar
                           <* updateLastPreCharPos
@@ -124,7 +180,7 @@
 
 str :: PandocMonad m => OrgParser m (F Inlines)
 str = return . B.str <$>
-      ( many1Char (noneOf $ specialChars ++ "\n\r ") >>= updatePositions' )
+      ( takeWhile1P (not . isStrEnd) >>= updatePositions' )
       <* updateLastStrPos
   where
     updatePositions' str' = str' <$
@@ -222,7 +278,7 @@
 orgCiteKey :: PandocMonad m => OrgParser m Text
 orgCiteKey = do
   char '@'
-  T.pack <$> many1 (satisfy orgCiteKeyChar)
+  takeWhile1P orgCiteKeyChar
 
 orgCiteKeyChar :: Char -> Bool
 orgCiteKeyChar c =
@@ -409,7 +465,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 +557,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 +603,7 @@
    orgInlineParamValue = try $
      skipSpaces
        *> notFollowedBy (char ':')
-       *> many1Char (noneOf "\t\n\r ]")
+       *> takeWhile1P (`notElem` ("\t\n\r ]" :: [Char]))
        <* skipSpaces
 
 
@@ -801,21 +857,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 +952,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 +983,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])
@@ -304,7 +349,8 @@
   indent <- length <$> many (char ' ')
   guard $ indent >= minIndent
   char ':'
-  name <- many1TillChar (noneOf "\n") (char ':')
+  name <- takeWhile1P (\c -> c /= '\n' && c /= ':')
+  char ':'
   (void (lookAhead newline)) <|> skipMany1 spaceChar
   first <- anyLine
   rest <- option "" $ try $ do lookAhead (count indent (char ' ') >> spaceChar)
@@ -433,7 +479,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 +511,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 +519,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 +1097,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 +1222,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 +1281,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 +1321,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 +1560,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 +1606,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 +1767,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 +1789,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 +1832,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
@@ -82,6 +82,12 @@
                                                  -- paragraph numbering is in
                                                  -- effect; every paragraph in
                                                  -- scope is its own list item
+                          , sPendingSurrogate :: Maybe Word16
+                                                 -- A high surrogate from a \u
+                                                 -- control word, held back
+                                                 -- until we know whether the
+                                                 -- low surrogate completing it
+                                                 -- follows
                           } deriving (Show)
 
 instance Default RTFState where
@@ -99,6 +105,7 @@
                 , sEatChars = 0
                 , sListText = False
                 , sPnActive = False
+                , sPendingSurrogate = Nothing
                 }
 
 type FontTable = IntMap.IntMap FontFamily
@@ -286,11 +293,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
@@ -385,12 +392,46 @@
 
 addText :: PandocMonad m => Text -> RTFParser m ()
 addText t = do
+  -- A high surrogate held back by 'addUnicodeCodeUnit' can only be completed
+  -- by the next \u control word, so any text in between means it was
+  -- unpaired.  Empty text does not count: consuming a \u's fallback
+  -- characters ends in adding the empty remainder of them, and that happens
+  -- between the two halves of a surrogate pair.
+  unless (T.null t) $
+    updateState $ \s -> s{ sPendingSurrogate = Nothing }
   gs <- sGroupStack <$> getState
   let !props = case gs of
                 (x:_) -> x
                 _ -> def
   updateState (\s -> s{ sTextContent = (props, t) : sTextContent s })
 
+-- | Add the character denoted by the argument of a @\\u@ control word, which
+-- is a single UTF-16 code unit.  A character outside the Basic Multilingual
+-- Plane is written as a surrogate pair, that is, as two consecutive @\\u@
+-- words, each followed by its own fallback characters, so a high surrogate is
+-- held back until we know whether the low surrogate completing it follows.
+-- An unpaired surrogate denotes no character and cannot be represented in
+-- 'Text', so it is dropped.
+addUnicodeCodeUnit :: PandocMonad m => Word16 -> RTFParser m ()
+addUnicodeCodeUnit unit = do
+  mbHigh <- sPendingSurrogate <$> getState
+  updateState $ \s -> s{ sPendingSurrogate = Nothing }
+  case mbHigh of
+    Just high | isLowSurrogate unit ->      -- a complete pair
+      addText $ T.singleton $ chr $
+        0x10000 + (fromIntegral high - 0xD800) * 0x400
+                + (fromIntegral unit - 0xDC00)
+    _ | isHighSurrogate unit ->             -- wait for the low surrogate
+          updateState $ \s -> s{ sPendingSurrogate = Just unit }
+      | isLowSurrogate unit -> pure ()      -- unpaired, so dropped
+      | otherwise -> addText $ T.singleton $ chr $ fromIntegral unit
+
+isHighSurrogate :: Word16 -> Bool
+isHighSurrogate unit = unit >= 0xD800 && unit <= 0xDBFF
+
+isLowSurrogate :: Word16 -> Bool
+isLowSurrogate unit = unit >= 0xDC00 && unit <= 0xDFFF
+
 inGroup :: PandocMonad m => RTFParser m a -> RTFParser m a
 inGroup p = do
   updateState $ \st ->
@@ -660,9 +701,7 @@
       -- "RTF control words generally accept signed 16-bit numbers as
       -- arguments. For this reason, Unicode values greater than 32767
       -- must be expressed as negative numbers."
-      let codepoint :: Word16
-          codepoint = fromIntegral i
-      addText (T.singleton (chr $ fromIntegral codepoint))
+      addUnicodeCodeUnit (fromIntegral i)
     ControlWord "caps" mbp -> bs <$
       modifyGroup (\g -> g{ gCaps = boolParam mbp })
     ControlWord "deleted" mbp -> bs <$
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
@@ -530,7 +530,8 @@
 
 regularText :: PandocMonad m => RoffLexer m [LinePart]
 regularText = do
-  s <- many1Char $ noneOf "\n\r\t \\\""
+  s <- takeWhile1P (\c -> not (c == '\n' || c == '\r' || c == '\t' ||
+                               c == ' ' || c == '\\' || c == '"'))
   return [RoffStr s]
 
 quoteChar :: PandocMonad m => RoffLexer m [LinePart]
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(..))
@@ -123,7 +123,7 @@
   case c of
     ' ' -> return $ emit " " -- mandoc_char(7) says this should be a nonbreaking space
     '"' -> mempty <$ skipMany (satisfy (/='\n')) -- line comment
-    '#' -> mempty <$ manyTill anyChar newline
+    '#' -> mempty <$ (skipMany (satisfy (/='\n')) <* newline)
     '%' -> return mempty  -- optional hyphenation
     '&' -> return mempty  -- nonprintable zero-width
     ')' -> return mempty  -- nonprintable zero-width
@@ -205,18 +205,18 @@
 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 (..
 escapeArg :: (PandocMonad m, RoffLikeLexer x) => Lexer m x T.Text
 escapeArg = choice
     [ char '[' *> optional expandString *>
-                  manyTillChar (noneOf ['\n',']']) (char ']')
+                  takeWhileP (\c -> c /= '\n' && c /= ']') <* char ']'
     , char '(' *> optional expandString *>
                   countChar 2 (satisfy (/='\n'))
     ]
 
 -- Parses: '..'
 quoteArg :: (PandocMonad m, RoffLikeLexer x) => Lexer m x T.Text
-quoteArg = char '\'' *> manyTillChar (noneOf ['\n','\'']) (char '\'')
+quoteArg = char '\'' *> takeWhile1P (\c -> c /= '\n' && c /= '\'') <* char '\''
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]
@@ -592,6 +593,76 @@
 wordBoundaries :: [Char]
 wordBoundaries = markupChars <> stringBreakers
 
+-- | True for the characters in 'wordBoundaries'.  Written as a @case@ so
+-- that GHC compiles it to a jump table; 'wordChunk' applies this to every
+-- single character of the input (at three separate sites), so a linear
+-- scan over the 39-element list here is a significant cost.  Must be kept
+-- in sync with 'markupChars' and 'stringBreakers'.
+isWordBoundary :: Char -> Bool
+isWordBoundary c = case c of
+  '\t'    -> True
+  '\n'    -> True
+  '\r'    -> True
+  ' '     -> True
+  '!'     -> True
+  '"'     -> True
+  '#'     -> True
+  '%'     -> True
+  '&'     -> True
+  '\''    -> True
+  '('     -> True
+  ')'     -> True
+  '*'     -> True
+  '+'     -> True
+  ','     -> True
+  '-'     -> True
+  '.'     -> True
+  ':'     -> True
+  ';'     -> True
+  '<'     -> True
+  '='     -> True
+  '>'     -> True
+  '?'     -> True
+  '@'     -> True
+  '['     -> True
+  '\\'    -> True
+  ']'     -> True
+  '^'     -> True
+  '_'     -> True
+  '|'     -> True
+  '~'     -> True
+  '\171'  -> True
+  '\187'  -> True
+  '\8216' -> True
+  '\8217' -> True
+  '\8218' -> True
+  '\8220' -> True
+  '\8221' -> True
+  '\8222' -> True
+  _       -> False
+
+-- | True for the characters in 'markupChars'.  As with 'isWordBoundary',
+-- written as a @case@ to get a jump table.  Must be kept in sync with
+-- 'markupChars'.
+isMarkupChar :: Char -> Bool
+isMarkupChar c = case c of
+  '#'  -> True
+  '%'  -> True
+  '&'  -> True
+  '*'  -> True
+  '+'  -> True
+  '-'  -> True
+  '='  -> True
+  '@'  -> True
+  '['  -> True
+  '\\' -> True
+  ']'  -> True
+  '^'  -> True
+  '_'  -> True
+  '|'  -> True
+  '~'  -> True
+  _    -> False
+
 -- | Parse a hyphened sequence of words
 hyphenedWords :: PandocMonad m => TextileParser m Text
 hyphenedWords = do
@@ -601,10 +672,10 @@
 
 wordChunk :: PandocMonad m => TextileParser m Text
 wordChunk = try $ do
-  hd <- noneOf wordBoundaries
-  tl <- many ( noneOf wordBoundaries <|>
-               try (notFollowedBy' note *> oneOf markupChars
-                     <* lookAhead (noneOf wordBoundaries) ) )
+  hd <- satisfy (not . isWordBoundary)
+  tl <- many ( satisfy (not . isWordBoundary) <|>
+               try (notFollowedBy' note *> satisfy isMarkupChar
+                     <* lookAhead (satisfy (not . isWordBoundary)) ) )
   return $ T.pack $ hd:tl
 
 -- | Any string
@@ -679,7 +750,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 ";*"
@@ -548,6 +550,27 @@
 specialChars :: [Char]
 specialChars = "%*-_/|:+;"
 
+-- | True for characters that terminate a run of plain text, i.e. the
+-- 'specialChars' plus newlines and spaces.  Written as a @case@ so that
+-- GHC compiles it to a jump table; this is applied to every single
+-- character of the input, so a linear scan over the list here is a
+-- significant cost.  Must be kept in sync with 'specialChars'.
+isStrEnd :: Char -> Bool
+isStrEnd c = case c of
+  '\n' -> True
+  '\r' -> True
+  ' '  -> True
+  '%'  -> True
+  '*'  -> True
+  '+'  -> True
+  '-'  -> True
+  '/'  -> True
+  ':'  -> True
+  ';'  -> True
+  '_'  -> True
+  '|'  -> True
+  _    -> False
+
 tab :: T2T Char
 tab = char '\t'
 
@@ -572,7 +595,7 @@
   return B.softbreak
 
 str :: T2T Inlines
-str = try $ B.str <$> many1Char (noneOf $ specialChars ++ "\n\r ")
+str = try $ B.str <$> takeWhile1P (not . isStrEnd)
 
 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
@@ -657,22 +737,17 @@
   let toWidth (VFraction f) = Just (floor $ 1000 * f)
       toWidth _ = Nothing
   let normalizeWidths xs =
+        -- keep column widths all-or-nothing for the writers: a
+        -- column the source leaves to the layout engine makes the
+        -- whole table content-sized
         let givenwidths = catMaybes xs
-            (totgivenwidth :: Int) = sum givenwidths
-            avgwidth = totgivenwidth `div` length givenwidths
-            totwidth = avgwidth * length xs
-         in if null givenwidths
-              then replicate (length xs) B.ColWidthDefault
-              else
+            totwidth = sum givenwidths :: Int
+         in if all isJust xs && totwidth > 0
+              then
                 map
-                  ( \case
-                      Just x ->
-                        B.ColWidth (fromIntegral x / fromIntegral totwidth)
-                      Nothing ->
-                        B.ColWidth
-                        (fromIntegral avgwidth / fromIntegral totwidth)
-                  )
-                  xs
+                  (\x -> B.ColWidth (fromIntegral x / fromIntegral totwidth))
+                  givenwidths
+              else replicate (length xs) B.ColWidthDefault
   widths <- case columns of
     VInteger x -> pure $ replicate (fromIntegral x) B.ColWidthDefault
     VArray x -> pure $ normalizeWidths $ map toWidth (V.toList x)
@@ -760,14 +835,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)
@@ -99,6 +101,35 @@
 spaceChars :: [Char]
 spaceChars = " \t\n"
 
+-- | True for characters that terminate a run of plain text, i.e. the
+-- 'spaceChars' plus the 'specialChars'.  Written as a @case@ so that GHC
+-- compiles it to a jump table; this is applied to every single character
+-- of the input, so a linear scan over the list here is a significant
+-- cost.  Must be kept in sync with 'spaceChars' and 'specialChars'.
+isStrEnd :: Char -> Bool
+isStrEnd c = case c of
+  '\t' -> True
+  '\n' -> True
+  ' '  -> True
+  '#'  -> True
+  '$'  -> True
+  '%'  -> True
+  '*'  -> True
+  ','  -> True
+  '-'  -> True
+  ':'  -> True
+  '='  -> True
+  '['  -> True
+  ']'  -> True
+  '^'  -> True
+  '_'  -> True
+  '`'  -> True
+  '{'  -> True
+  '|'  -> True
+  '}'  -> True
+  '~'  -> True
+  _    -> False
+
 -- main parser
 
 parseVimwiki :: PandocMonad m => VwParser m Pandoc
@@ -200,7 +231,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 +254,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 +264,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 +513,7 @@
 inlineML = choice $ whitespace endlineML:inlineList
 
 str :: PandocMonad m => VwParser m Inlines
-str = B.str <$> many1Char (noneOf $ spaceChars ++ specialChars)
+str = B.str <$> takeWhile1P (not . isStrEnd)
 
 whitespace :: PandocMonad m => VwParser m () -> VwParser m Inlines
 whitespace endline = B.space <$ (skipMany1 spaceChar <|>
@@ -507,7 +541,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, isUnreserved)
 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 isUnreserved (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/Templates.hs b/src/Text/Pandoc/Templates.hs
--- a/src/Text/Pandoc/Templates.hs
+++ b/src/Text/Pandoc/Templates.hs
@@ -108,6 +108,7 @@
        "asciidoc_legacy" -> getDefaultTemplate "asciidoc"
        "docx"    -> getDefaultTemplate "openxml"
        "odt"     -> getDefaultTemplate "opendocument"
+       "fodt"    -> getDefaultTemplate "opendocument"
        "html"    -> getDefaultTemplate "html5"
        "docbook" -> getDefaultTemplate "docbook5"
        "epub"    -> getDefaultTemplate "epub3"
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.hs b/src/Text/Pandoc/Writers.hs
--- a/src/Text/Pandoc/Writers.hs
+++ b/src/Text/Pandoc/Writers.hs
@@ -61,6 +61,7 @@
     , writeMuse
     , writeNative
     , writeODT
+    , writeFODT
     , writeOPML
     , writeOpenDocument
     , writeOrg
@@ -161,6 +162,7 @@
   ,("json"         , TextWriter writeJSON)
   ,("docx"         , ByteStringWriter writeDocx)
   ,("odt"          , ByteStringWriter writeODT)
+  ,("fodt"         , TextWriter writeFODT)
   ,("pptx"         , ByteStringWriter writePowerpoint)
   ,("epub"         , ByteStringWriter writeEPUB3)
   ,("epub2"        , ByteStringWriter writeEPUB2)
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,12 +25,12 @@
 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)
 import qualified Data.Map as M
-import Data.Maybe (fromMaybe, maybeToList, isJust)
+import Data.Maybe (fromMaybe, maybeToList, isJust, listToMaybe)
 import Control.Monad.State ( gets, modify, MonadTrans(lift) )
 import Control.Monad.Reader ( asks, MonadReader(local) )
 import qualified Data.Set as Set
@@ -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,44 @@
 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
+-- A section's bookmark goes in its heading's paragraph, around the
+-- heading's text, as Word writes one: it then marks the heading rather
+-- than the whole section (#11845, #8825), and the docx reader, which
+-- reads bookmarks only inside paragraphs, finds it, so a link to the
+-- heading survives a round trip.
+blockToOpenXML' opts (Div (ident,classes,kvs) (Header lev ("",hcls,hkvs) ils : bs))
+  | "section" `elem` classes
+  , not (T.null ident)
+  = blockToOpenXML' opts
+      (Div ("",classes,kvs) (Header lev (ident,hcls,hkvs) ils : bs))
+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 +468,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 +540,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 +771,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 +797,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]
 
@@ -747,6 +840,69 @@
   props <- getTextProps
   return $ mknode "w:r" [] $ props ++ els
 
+-- | Inject a highlight into every run of a converted math element:
+-- glyph runs in a @w:rPr@ child, and a structure's non-glyph parts
+-- (bars, operator spacing) in the @m:ctrlPr@ of its property element
+-- (@m:sSupPr@ and the like), which Word pens when an equation is
+-- highlighted by hand and which the converters omit when empty.
+highlightMathRuns :: XML.Element -> XML.Element -> XML.Element
+highlightMathRuns hl = go
+ where
+  isTag pfx n e = XML.qName (XML.elName e) == n
+              && XML.qPrefix (XML.elName e) == Just pfx
+  go e
+    | isTag "m" "r" e || isTag "m" "ctrlPr" e =
+        e{ XML.elContent = insertHl (map walkContent (XML.elContent e)) }
+    | Just "m" <- XML.qPrefix (XML.elName e)
+    , XML.qName (XML.elName e) `elem` structures =
+        e{ XML.elContent =
+             ensureCtrlPr (XML.qName (XML.elName e) <> "Pr")
+                          (map walkContent (XML.elContent e)) }
+    | otherwise = e{ XML.elContent = map walkContent (XML.elContent e) }
+  walkContent (XML.Elem e) = XML.Elem (go e)
+  walkContent c            = c
+  -- OMML structures (ECMA-376 22.1.2); each takes an m:<name>Pr
+  -- property element as its first child
+  structures =
+    [ "sSup", "sSub", "sSubSup", "sPre"
+    , "f", "nary", "d", "rad", "func", "groupChr"
+    , "limLow", "limUpp", "m", "eqArr", "bar"
+    , "phant", "box", "borderBox", "acc" ]
+  pennedCtrlPr = XML.Elem (mknode "m:ctrlPr" [] (mknode "w:rPr" [] hl))
+  ensureCtrlPr prName cs = case cs of
+    (XML.Elem e : rest) | isTag "m" prName e ->
+      if any isCtrlPr (XML.elContent e)
+        then cs
+        else XML.Elem e{ XML.elContent = XML.elContent e ++ [pennedCtrlPr] } : rest
+    _ -> XML.Elem (mknode ("m:" <> prName) [] pennedCtrlPr) : cs
+  isCtrlPr (XML.Elem e) = isTag "m" "ctrlPr" e
+  isCtrlPr _            = False
+  -- merge into an existing w:rPr child if there is one, else insert a
+  -- fresh w:rPr after an optional m:rPr
+  insertHl cs =
+    case break isWrPr cs of
+      (before, XML.Elem e : rest) ->
+        before ++ XML.Elem e{ XML.elContent = insertInRPr (XML.elContent e) } : rest
+      _ ->
+        case cs of
+          (XML.Elem e : rest) | isTag "m" "rPr" e ->
+            XML.Elem e : wrPr : rest
+          _ -> wrPr : cs
+  -- w:rPr children come in a fixed order (rPrTagOrder): the
+  -- highlight goes before the first child that must follow it, and
+  -- after children not in the order
+  insertInRPr [] = [XML.Elem hl]
+  insertInRPr (c:rest)
+    | followsHl c = XML.Elem hl : c : rest
+    | otherwise = c : insertInRPr rest
+  followsHl (XML.Elem e) =
+    maybe False (> rPrTagOrder M.! "highlight") $
+      M.lookup (XML.qName (XML.elName e)) rPrTagOrder
+  followsHl _ = False
+  isWrPr (XML.Elem e) = isTag "w" "rPr" e
+  isWrPr _            = False
+  wrPr = XML.Elem (mknode "w:rPr" [] hl)
+
 -- | Convert an inline element to OpenXML.
 inlineToOpenXML :: PandocMonad m => WriterOptions -> Inline -> WS m [Content]
 inlineToOpenXML opts il = withDirection $ inlineToOpenXML' opts il
@@ -754,8 +910,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 +929,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 =
@@ -883,19 +1036,25 @@
   when (mathType == DisplayMath) setFirstPara
   res <- (lift . lift) (convertMath writeOMML mathType str)
   case res of
-       Right r -> return [Elem $ fromXLElement r]
+       Right r -> do
+         tprops <- asks envTextProperties
+         let r' = fromXLElement r
+             mbhl = listToMaybe
+               [ e | e <- otherElements tprops
+                    , XML.qName (XML.elName e) == "highlight"
+                    , XML.qPrefix (XML.elName e) == Just "w" ]
+         return [Elem (maybe r' (`highlightMathRuns` r') mbhl)]
        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 +1070,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 +1085,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 +1107,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 +1154,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 +1213,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 +1274,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 +1305,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/StyleMap.hs b/src/Text/Pandoc/Writers/Docx/StyleMap.hs
--- a/src/Text/Pandoc/Writers/Docx/StyleMap.hs
+++ b/src/Text/Pandoc/Writers/Docx/StyleMap.hs
@@ -16,6 +16,7 @@
 module Text.Pandoc.Writers.Docx.StyleMap ( StyleMaps(..)
                                          , ParaStyleName
                                          , CharStyleName
+                                         , TableStyleName
                                          , getStyleMaps
                                          , getStyleIdFromName
                                          , hasStyleName
@@ -30,7 +31,10 @@
 import Data.String
 import Data.Char (isSpace)
 
-data StyleMaps = StyleMaps { smCharStyle :: CharStyleNameMap, smParaStyle :: ParaStyleNameMap }
+data StyleMaps = StyleMaps { smCharStyle  :: CharStyleNameMap
+                           , smParaStyle  :: ParaStyleNameMap
+                           , smTableStyle :: TableStyleNameMap }
+type TableStyleNameMap = M.Map TableStyleName TableStyle
 type ParaStyleNameMap = M.Map ParaStyleName ParStyle
 type CharStyleNameMap = M.Map CharStyleName CharStyle
 
@@ -44,4 +48,6 @@
 hasStyleName styleName = M.member styleName
 
 getStyleMaps :: Archive -> StyleMaps
-getStyleMaps = uncurry StyleMaps . archiveToStyles' getStyleName getStyleName
+getStyleMaps arch =
+  let (s1, s2, s3) = archiveToStyles' getStyleName getStyleName getStyleName arch
+  in  StyleMaps s1 s2 s3
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
@@ -23,6 +23,7 @@
 import Data.Array ( elems, (!), assocs, indices )
 import Data.Text (Text)
 import Data.Maybe (catMaybes, fromMaybe)
+import Data.String
 import Text.Pandoc.Definition
     ( ColSpec,
       Caption(Caption),
@@ -42,6 +43,7 @@
       WriterEnv(..),
       setFirstPara,
       pStyleM,
+      tblStyleM,
       withParaProp,
       withParaPropM )
 import Control.Monad.Reader (asks)
@@ -110,11 +112,12 @@
   let (gridCols, tblWattr) = tableLayout (elems colspecs)
   listLevel <- asks envListLevel
   let tblStyle =  fromMaybe "Table" (lookup "custom-style" tableAttr)
+  sty' <- tblStyleM (fromString $ T.unpack tblStyle)
   let indent = (listLevel + 1) * 720
   let hasWidths = not $ all ((== ColWidthDefault) . snd) colspecs
   let tbl = mknode "w:tbl" []
         ( mknode "w:tblPr" []
-          ( [ mknode "w:tblStyle" [("w:val",tblStyle)] (),
+          ( [ sty',
               mknode "w:tblW" tblWattr () ] ++
             [ mknode "w:jc" [("w:val","left")] () | indent > 0 ] ++
             [ mknode "w:tblInd" [("w:w", tshow indent),("w:type","dxa")] ()
@@ -178,7 +181,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
@@ -17,6 +17,7 @@
   , ListMarker (..)
   , listMarkerToId
   , pStyleM
+  , tblStyleM
   , isStyle
   , setFirstPara
   , withParaProp
@@ -27,6 +28,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 +70,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 +89,7 @@
   , envInNote         :: Bool
   , envChangesAuthor  :: Text
   , envChangesDate    :: Text
-  , envPrintWidth     :: Integer
+  , envPrintWidth     :: Integer  -- in points
   , envLang           :: Maybe Text
   , envSectPr         :: Maybe Element
   }
@@ -120,6 +122,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.
@@ -145,7 +150,8 @@
       , stExampleId      = Nothing
       , stInsId          = 1
       , stDelId          = 1
-      , stStyleMaps      = StyleMaps M.empty M.empty
+      , stStyleMaps      = StyleMaps M.empty M.empty M.empty
+      , stTokTypesMap    = M.empty
       , stFirstPara      = False
       , stFirstSectionHeader = True
       , stNumIdUsed      = False
@@ -195,6 +201,12 @@
 withParaPropM md p = do
   d <- md
   withParaProp d p
+
+tblStyleM :: (PandocMonad m) => TableStyleName -> WS m XML.Element
+tblStyleM styleName = do
+  tStyleMap <- gets (smTableStyle . stStyleMaps)
+  let sty' = getStyleIdFromName styleName tStyleMap
+  return $ mknode "w:tblStyle" [("w:val", fromStyleId sty')] ()
 
 isStyle :: Element -> Bool
 isStyle e = isElem [] "w" "rStyle" e ||
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
@@ -467,7 +479,7 @@
                   defField "slidy-url"
                     ("https://www.w3.org/Talks/Tools/Slidy2" :: Doc Text) .
                   defField "slideous-url" ("slideous" :: Doc Text) .
-                  defField "revealjs-url" ("https://unpkg.com/reveal.js@^5" :: Doc Text) $
+                  defField "revealjs-url" ("https://unpkg.com/reveal.js@^6" :: Doc Text) $
                   defField "s5-url" ("s5/default" :: Doc Text) .
                   defField "table-caption-below"
                      (writerTableCaptionPosition opts == CaptionBelow) .
@@ -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)   ->
@@ -1209,7 +1205,8 @@
                   _ -> ["keepaspectratio"]) <>
                 maybe [] (\x -> ["page=" <> literal x]) (lookup "page" kvs) <>
                 maybe [] (\x -> ["trim=" <> literal x]) (lookup "trim" kvs) <>
-                maybe [] (\x -> ["alt=" <> braces (literal x)]) mbalt <>
+                maybe [] (\x -> ["alt=" <>
+                              braces (hsep $ map literal $ T.words x)]) mbalt <>
                 maybe [] (const ["clip"]) (lookup "clip" kvs)
       options = if null optList
                    then empty
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
@@ -50,7 +50,8 @@
 import Text.Pandoc.Writers.Markdown.Inline (inlineListToMarkdown,
                                             linkAttributes,
                                             attrsToMarkdown,
-                                            attrsToMarkua)
+                                            attrsToMarkua,
+                                            linkDestination)
 import Text.Pandoc.Writers.Markdown.Table (pipeTable, pandocTable)
 import Text.Pandoc.Writers.Markdown.Types (MarkdownVariant(..),
                                            WriterState(..),
@@ -290,7 +291,8 @@
                 then empty
                 else space <> "\"" <> literal tit <> "\""
   return $ nest 2 $ hang 2
-            ("[" <> literal label' <> "]:" <> space) (literal src <> tit')
+            ("[" <> literal label' <> "]:" <> space)
+            (literal (linkDestination src) <> tit')
             <+> linkAttributes opts attr
 
 -- | Return markdown representation of notes.
@@ -429,28 +431,39 @@
          where (id',classes',kvs') = attrs
                attrs' = (id',classes',("markdown","1"):kvs')
 blockToMarkdown' opts (Plain inlines) = do
-  -- escape if para starts with ordered list marker
+  -- escape if a line starts with a list marker.  This must be done at the
+  -- start of every line, not just the start of the block: a sublist can
+  -- interrupt a list item's text after a LineBreak, so an unescaped marker
+  -- there would be read back as a nested list, destroying the LineBreak.
   variant <- asks envVariant
   let escapeMarker = T.concatMap $ \x -> if T.any (== x) ".()"
                                          then T.pack ['\\', x]
                                          else T.singleton x
   let startsWithSpace (Space:_)     = True
       startsWithSpace (SoftBreak:_) = True
+      startsWithSpace (LineBreak:_) = True
       startsWithSpace _             = False
+  let escapeLineStart ils =
+        case ils of
+          (Str t:ys)
+            | null ys || startsWithSpace ys
+            , beginsWithOrderedListMarker t
+            -> RawInline (Format "markdown") (escapeMarker t):ys
+          (Str t:_)
+            | t == "+" || t == "-" ||
+              (t == "%" && isEnabled Ext_pandoc_title_block opts &&
+                           isEnabled Ext_all_symbols_escapable opts)
+            -> RawInline (Format "markdown") "\\" : ils
+          _ -> ils
+  -- apply escapeLineStart to the head of the list, then after each LineBreak
+  let go ils = case ils of
+                 []             -> []
+                 (LineBreak:ys) -> LineBreak : go (escapeLineStart ys)
+                 (y:ys)         -> y : go ys
   let inlines' =
         if variant == PlainText
            then inlines
-           else case inlines of
-                  (Str t:ys)
-                    | null ys || startsWithSpace ys
-                    , beginsWithOrderedListMarker t
-                    -> RawInline (Format "markdown") (escapeMarker t):ys
-                  (Str t:_)
-                    | t == "+" || t == "-" ||
-                      (t == "%" && isEnabled Ext_pandoc_title_block opts &&
-                                   isEnabled Ext_all_symbols_escapable opts)
-                    -> RawInline (Format "markdown") "\\" : inlines
-                  _ -> inlines
+           else go (escapeLineStart inlines)
   contents <- inlineListToMarkdown opts inlines'
   return $ contents <> cr
 blockToMarkdown' opts (Para inlines) =
@@ -672,7 +685,7 @@
          isEnabled Ext_pipe_tables opts -> do
            tbl <- mkTable (pipeTable opts)
            return $ (tbl $$ caption''') $$ blankline
-       | not (hasBlocks || hasColRowSpans || hasFooter) &&
+       | not (hasBlocks || hasColRowSpans || hasFooter || null rows) &&
          isEnabled Ext_multiline_tables opts -> do
            tbl <- mkTable (pandocTable opts True)
            return $ nest 2 (tbl $$ caption''') $$ blankline
@@ -754,8 +767,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
@@ -14,14 +14,15 @@
   inlineListToMarkdown,
   linkAttributes,
   attrsToMarkdown,
-  attrsToMarkua
+  attrsToMarkua,
+  linkDestination
   ) where
 import Control.Monad (when, liftM2)
 import Control.Monad.Reader
     ( asks, MonadReader(local) )
 import Control.Monad.State.Strict
     ( MonadState(get), gets, modify )
-import Data.Char (isAlphaNum, isDigit)
+import Data.Char (isAlphaNum, isDigit, isSpace)
 import Data.List (find, intersperse)
 import Data.List.NonEmpty (nonEmpty)
 import qualified Data.Map as M
@@ -86,7 +87,7 @@
   go ('\\':c:cs)
     | isEnabled Ext_raw_tex opts = '\\':'\\':go (c:cs)
     | isAlphaNum c = '\\' : go (c:cs)
-    | otherwise = '\\':'\\': go cs
+    | otherwise = '\\':'\\': go (c:cs)
   go ('!':'[':cs) = '\\':'!':'[': go cs
   go ('=':'=':cs)
     | isEnabled Ext_mark opts = '\\':'=':go ('=':cs)
@@ -338,6 +339,17 @@
   toList (Concat a b) = a : toList b
   toList x = [x]
 
+-- | Render a link destination, enclosing it in angle brackets if
+-- it contains whitespace, which is not allowed in a bare destination.
+linkDestination :: Text -> Text
+linkDestination src
+  | T.any isSpace src = "<" <> T.concatMap escapeChar src <> ">"
+  | otherwise = src
+  where
+    escapeChar c
+      | c `elem` ['<', '>', '\\'] = T.pack ['\\', c]
+      | otherwise = T.singleton c
+
 -- | Convert Pandoc inline element to markdown.
 inlineToMarkdown :: PandocMonad m => WriterOptions -> Inline -> MD m (Doc Text)
 inlineToMarkdown opts (Span ("",["emoji"],kvs) [Str s]) =
@@ -678,13 +690,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
@@ -700,8 +712,8 @@
             writeHtml5String opts{ writerTemplate = Nothing }
             (Pandoc nullMeta [Plain [lnk]])
       | otherwise -> return $
-         "[" <> linktext <> "](" <> literal src <> linktitle <> ")" <>
-         linkAttributes opts attr
+         "[" <> linktext <> "](" <> literal (linkDestination src) <> linktitle
+         <> ")" <> linkAttributes opts attr
 inlineToMarkdown opts img@(Image attr alternate (source, tit))
   | isEnabled Ext_raw_html opts &&
     not (isEnabled Ext_link_attributes opts || isEnabled Ext_attributes opts) &&
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
@@ -11,15 +11,16 @@
 
 Conversion of 'Pandoc' documents to ODT.
 -}
-module Text.Pandoc.Writers.ODT ( writeODT ) where
+module Text.Pandoc.Writers.ODT ( writeODT, writeFODT ) where
 import Codec.Archive.Zip
 import Control.Monad.Except (catchError, throwError)
 import Control.Monad.State.Strict (StateT, evalStateT, gets, modify, lift)
 import Control.Monad (MonadPlus(mplus))
+import Data.ByteString.Base64 (encode)
 import qualified Data.ByteString.Lazy as B
-import Data.Maybe (fromMaybe, listToMaybe)
+import Data.Maybe (catMaybes, fromMaybe, listToMaybe, mapMaybe, maybeToList)
 import Data.Generics (everywhere', mkT)
-import Data.List (isPrefixOf)
+import Data.List (isPrefixOf, partition)
 import qualified Data.Map as Map
 import qualified Data.Text as T
 import qualified Data.Text.Lazy as TL
@@ -38,22 +39,29 @@
                             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.Templates (compileDefaultTemplate)
 import Text.Pandoc.Version (pandocVersionText)
 import Text.Pandoc.Writers.Shared (lookupMetaString, lookupMetaBlocks,
                                    fixDisplayMath, getLang,
                                    ensureValidXmlIdentifiers)
-import Text.Pandoc.UTF8 (fromStringLazy, fromTextLazy, toTextLazy)
+import Text.Pandoc.UTF8 (fromStringLazy, fromTextLazy, toText, toTextLazy)
 import Text.Pandoc.Walk
 import Text.Pandoc.Writers.OpenDocument (writeOpenDocument)
 import Text.Pandoc.XML
 import Text.Pandoc.XML.Light
+import qualified Text.Pandoc.XML.Light as XML
 import Text.TeXMath
 import qualified Text.XML.Light as XL
 import Network.URI (parseRelativeReference, URI(uriPath), isURI)
 import Skylighting
 
-newtype ODTState = ODTState { stEntries :: [Entry]
+-- | ODF can be written either as a zip archive of several XML files or
+-- as a single flat XML file.
+data ODTVariant = Zipped | Flat deriving (Eq, Show)
+
+data ODTState = ODTState { stEntries :: [Entry]
+                         , stVariant :: ODTVariant
                          }
 
 type O m = StateT ODTState m
@@ -65,14 +73,16 @@
          -> m B.ByteString
 writeODT  opts doc =
   let initState = ODTState{ stEntries = []
+                          , stVariant = Zipped
                           }
       doc' = fixInternalLinks . ensureValidXmlIdentifiers $ doc
   in
-    evalStateT (pandocToODT opts doc') initState
+    fromArchive <$> evalStateT (pandocToODT opts doc') initState
 
 -- | ODT internal links are evaluated relative to an imaginary folder
 -- structure that mirrors the zip structure.  The result is that relative
 -- links in the document need to start with `..`.  See #3524.
+-- This does not apply to flat ODF, which is a single file.
 fixInternalLinks :: Pandoc -> Pandoc
 fixInternalLinks = walk go
  where
@@ -85,11 +95,209 @@
         | not (null (uriPath u)) -> tshow $ u{ uriPath = "../" <> uriPath u }
       _ -> uri
 
--- | Produce an ODT file from a Pandoc document.
+-- | Produce a flat OpenDocument text file (@.fodt@) from a Pandoc
+-- document.  This is the same content as an ODT, but as a single XML
+-- file instead of a zip archive.
+writeFODT :: PandocMonad m
+          => WriterOptions  -- ^ Writer options
+          -> Pandoc         -- ^ Document to convert
+          -> m T.Text
+writeFODT opts doc = do
+  let initState = ODTState{ stEntries = []
+                          , stVariant = Flat
+                          }
+      -- note: no fixInternalLinks, since a flat file has no zip
+      -- structure for relative references to be resolved against
+      doc' = ensureValidXmlIdentifiers doc
+  evalStateT (pandocToODT opts doc') initState >>= flattenODT
+
+officeNS :: T.Text
+officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"
+
+officeQName :: T.Text -> QName
+officeQName n = QName n (Just officeNS) (Just "office")
+
+-- | The top-level child of an ODF part, e.g. @office:body@ of
+-- @content.xml@.  Only the local name is matched if the document gives
+-- no namespace URI.
+officeChild :: T.Text -> Element -> Maybe Element
+officeChild name el = listToMaybe
+  [ e | Elem e <- elContent el
+      , qName (elName e) == name
+      , maybe True (== officeNS) (qURI (elName e)) ]
+
+-- | Convert an ODT archive into the equivalent flat OpenDocument file.
+--
+-- A flat ODF file is a single @office:document@ element whose children
+-- are exactly the top-level elements that are otherwise distributed over
+-- @content.xml@, @styles.xml@, @meta.xml@ and @settings.xml@, in this
+-- order:
+--
+-- > office:meta? office:settings? office:scripts? office:font-face-decls?
+-- > office:styles? office:automatic-styles? office:master-styles? office:body
+--
+-- Anything that would be a separate archive entry -- images and
+-- formulas -- is embedded inline instead.
+flattenODT :: forall m . PandocMonad m => Archive -> m T.Text
+flattenODT archive = do
+  contentRoot <- getRoot "content.xml"
+  stylesRoot  <- getRoot "styles.xml"
+  metaRoot    <- getRoot "meta.xml"
+  -- the bundled reference.odt has no settings.xml, but a reference doc
+  -- written by LibreOffice does
+  settingsRoot <- traverse parseEntry (findEntryByPath "settings.xml" archive)
+  let roots = [contentRoot, stylesRoot, metaRoot] ++ maybeToList settingsRoot
+  -- only one of each of these is allowed in a document, so the ones
+  -- from content.xml and styles.xml must be combined
+  let (fontFaceDecls, _) = mergeParts (officeChild "font-face-decls" contentRoot)
+                                      (officeChild "font-face-decls" stylesRoot)
+  let (automaticStyles, dropped) =
+        mergeParts (officeChild "automatic-styles" contentRoot)
+                   (officeChild "automatic-styles" stylesRoot)
+  mapM_ (\e -> report $ UnusualConversion $
+                 "flat OpenDocument output: dropping the automatic style "
+                 <> snd (partKey e) <> " from styles.xml, because content.xml "
+                 <> "defines a style of the same name")
+        dropped
+  let children = catMaybes
+        [ officeChild "meta" metaRoot
+        , settingsRoot >>= officeChild "settings"
+        , listToMaybe $ mapMaybe (officeChild "scripts") roots
+        , fontFaceDecls
+        , officeChild "styles" stylesRoot
+        , automaticStyles
+        , officeChild "master-styles" stylesRoot
+        , officeChild "body" contentRoot
+        ]
+  let root = Element (officeQName "document")
+               ( Attr (officeQName "mimetype")
+                      "application/vnd.oasis.opendocument.text"
+               : Attr (officeQName "version") "1.3"
+               : nsDecls roots )
+               (map (Elem . embedArchiveRefs archive) children)
+               Nothing
+  -- note: not prettyConfigPP, which would inject whitespace into text
+  -- content and thereby change the document
+  return $ showTopElement root
+ where
+  parseEntry :: Entry -> m Element
+  parseEntry e =
+    case parseXMLElement (toTextLazy (fromEntry e)) of
+      Left msg -> throwError $ PandocXMLError (T.pack (eRelativePath e)) msg
+      Right el -> return el
+  getRoot :: FilePath -> m Element
+  getRoot path =
+    case findEntryByPath path archive of
+      Nothing -> throwError $ PandocSomeError $
+                   "Could not find " <> T.pack path <> " in the ODT archive"
+      Just e  -> parseEntry e
+
+-- | The union of the namespace declarations on the given elements.
+-- Declarations survive parsing as ordinary attributes with the prefix
+-- @xmlns@, and are not synthesised on output, so they must be carried
+-- over to the flat file's root by hand.  Declarations on nested elements
+-- are copied along with those elements.
+nsDecls :: [Element] -> [XML.Attr]
+nsDecls els = Map.elems $ Map.fromListWith (\_new old -> old)
+  [ ((qPrefix (attrKey a), qName (attrKey a)), a)
+  | el <- els
+  , a <- elAttribs el
+  , isNSDecl (attrKey a) ]
+ where
+  isNSDecl qn = qPrefix qn == Just "xmlns"
+                || (qPrefix qn == Nothing && qName qn == "xmlns")
+
+-- | Combine two @office:font-face-decls@ or @office:automatic-styles@
+-- elements, preferring the first.  Returns the merged element and the
+-- declarations of the second that were dropped as duplicates.
+mergeParts :: Maybe Element -> Maybe Element -> (Maybe Element, [Element])
+mergeParts Nothing  mbSecond = (mbSecond, [])
+mergeParts mbFirst  Nothing  = (mbFirst, [])
+mergeParts (Just first) (Just second) =
+  (Just first{ elContent = elContent first ++ map Elem kept }, dropped)
+ where
+  keys = map partKey [ e | Elem e <- elContent first ]
+  (dropped, kept) = partition ((`elem` keys) . partKey)
+                              [ e | Elem e <- elContent second ]
+
+-- | Identify a font face or automatic style by its element name and
+-- @style:name@.
+partKey :: Element -> (T.Text, T.Text)
+partKey e = (qName (elName e), partStyleName e)
+
+partStyleName :: Element -> T.Text
+partStyleName e = T.concat [ attrVal a
+                           | a <- elAttribs e
+                           , qName (attrKey a) == "name"
+                           , qPrefix (attrKey a) == Just "style" ]
+
+-- | Replace references to other entries of the ODT archive by the
+-- entries' contents, which is the only way a flat file can carry them:
+-- images become base64 in an @office:binary-data@ child, formulas
+-- become MathML inside the @draw:object@.
+--
+-- Note that this is applied to every copied part, not just the body, so
+-- that images used by a reference document's headers, footers and master
+-- styles are embedded too.  References that do not resolve to an archive
+-- entry are left alone; in particular, this is what makes
+-- @--link-images@ work, since it writes no @Pictures/@ entry.
+embedArchiveRefs :: Archive -> Element -> Element
+embedArchiveRefs archive = go
+ where
+  go el =
+    case qName (elName el) of
+      "image"
+        | Just entry <- flip findEntryByPath archive . T.unpack =<< xlinkHref el
+        -> embedImage entry el
+      "object"
+        | Just path <- formulaPath =<< xlinkHref el
+        , Just entry <- findEntryByPath path archive
+        , Right mathml <- parseXMLElement (toTextLazy (fromEntry entry))
+        -> (dropXlinkAttrs el){ elContent = [Elem mathml] }
+      _ -> el{ elContent = map goContent (elContent el) }
+  goContent (Elem e) = Elem (go e)
+  goContent c = c
+
+  embedImage entry el =
+    case dropXlinkAttrs el of
+      el' -> el'{ elAttribs = mimeAttrs ++ elAttribs el'
+                , elContent = elContent el' ++ [Elem binaryData] }
+   where
+    mimeAttrs = [ Attr (QName "mime-type" Nothing (Just "draw")) m
+                | Just m <- [getMimeType (eRelativePath entry)] ]
+    binaryData = Element (officeQName "binary-data") []
+                   [XML.Text (CData CDataText (base64 (fromEntry entry)) Nothing)]
+                   Nothing
+
+  base64 = T.intercalate "\n" . T.chunksOf 76 . toText . encode . B.toStrict
+
+  -- the formula's MathML lives in content.xml of the directory the
+  -- reference names
+  formulaPath href =
+    case fromMaybe href (T.stripPrefix "./" href) of
+      "" -> Nothing
+      h  -> Just $ T.unpack $
+              (if "/" `T.isSuffixOf` h then h else h <> "/") <> "content.xml"
+
+xlinkHref :: Element -> Maybe T.Text
+xlinkHref el = listToMaybe [ attrVal a
+                           | a <- elAttribs el
+                           , qName (attrKey a) == "href"
+                           , qPrefix (attrKey a) == Just "xlink" ]
+
+-- | Drop the attributes with which the ODT writer refers to an archive
+-- entry; the content is about to be embedded instead.
+dropXlinkAttrs :: Element -> Element
+dropXlinkAttrs el = el{ elAttribs = filter (not . isRef . attrKey) (elAttribs el) }
+ where
+  isRef qn = qPrefix qn == Just "xlink"
+             && qName qn `elem` ["href", "type", "show", "actuate"]
+
+-- | Produce an ODT archive from a Pandoc document.
 pandocToODT :: PandocMonad m
             => WriterOptions  -- ^ Writer options
             -> Pandoc         -- ^ Document to convert
-            -> O m B.ByteString
+            -> O m Archive
 pandocToODT opts doc@(Pandoc meta _) = do
   let title = docTitle meta
   let authors = docAuthors meta
@@ -105,7 +313,10 @@
   -- picEntriesRef <- P.newIORef ([] :: [Entry])
   let refTextWidth = referenceTextWidthPt opts refArchive
   doc' <- walkM (transformPicMath opts refTextWidth) $ walk fixDisplayMath doc
-  newContents <- lift $ writeOpenDocument opts{writerWrapText = WrapNone} doc'
+  tpl <- maybe (lift $ compileDefaultTemplate "opendocument") pure
+           (writerTemplate opts)
+  newContents <- lift $ writeOpenDocument
+                   opts{ writerWrapText = WrapNone, writerTemplate = Just tpl } doc'
   epochtime <- floor `fmap` lift P.getPOSIXTime
   let contentEntry = toEntry "content.xml" epochtime
                      $ fromTextLazy $ TL.fromStrict newContents
@@ -165,7 +376,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 +395,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
                  )
@@ -195,7 +406,7 @@
   archive'' <- updateStyle opts lang
                   $ addEntryToArchive mimetypeEntry
                   $ addEntryToArchive metaEntry archive'
-  return $ fromArchive archive''
+  return archive''
 
 updateStyle :: forall m . PandocMonad m
             => WriterOptions -> Maybe Lang -> Archive -> O m Archive
@@ -367,7 +578,13 @@
                     case T.unpack src of
                       s | isURI s -> return src
                         | isAbsolute s -> return src
-                        | otherwise -> return $ T.pack $ ".." </> s
+                        | otherwise -> do
+                            -- see fixInternalLinks: relative references
+                            -- are resolved against the zip structure
+                            variant <- gets stVariant
+                            case variant of
+                              Zipped -> return $ T.pack $ ".." </> s
+                              Flat   -> return $ T.pack s
                   else do
                     entries <- gets stEntries
                     let extension = maybe (takeExtension $ takeWhile (/='?') $ T.unpack src) T.unpack
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/OpenDocument.hs b/src/Text/Pandoc/Writers/OpenDocument.hs
--- a/src/Text/Pandoc/Writers/OpenDocument.hs
+++ b/src/Text/Pandoc/Writers/OpenDocument.hs
@@ -14,11 +14,9 @@
 Conversion of 'Pandoc' documents to OpenDocument XML.
 -}
 module Text.Pandoc.Writers.OpenDocument ( writeOpenDocument ) where
-import Control.Arrow ((***), (>>>))
-import Control.Monad (unless, liftM)
+import Control.Monad (unless, liftM, when)
 import Control.Monad.State.Strict ( StateT(..), modify, gets, lift )
 import Data.Char (chr, isDigit)
-import Data.Foldable (find)
 import Data.List (sortOn, sortBy)
 import qualified Data.List as L
 import qualified Data.Map as Map
@@ -89,63 +87,65 @@
   deriving (Show, Eq, Ord)
 
 data WriterState =
-    WriterState { stNotes          :: [Doc Text]
+    WriterState { stNoteCount      :: Int
                 , stTableStyles    :: [Doc Text]
+                , stTableStyleCount :: Int
                 , stParaStyles     :: [Doc Text]
+                , stParaStyleCount :: Int
+                , stParaStyleCache :: Map.Map ([(Text,Text)],[(Text,Text)]) Text
+                  -- ^ cache of automatic paragraph styles, keyed on
+                  -- (style attributes, paragraph properties)
                 , stListOverrides  :: Map.Map (ListNumberStyle,ListNumberDelim)
                                         (Text, Doc Text)
                 , stTextStyles     :: Map.Map (Set.Set TextStyle)
-                                        (Text, Doc Text)
+                                        (Int, Doc Text)
+                  -- ^ automatic text styles, with the number that
+                  -- makes up the style's @Tn@ name
                 , stTextStyleAttr  :: Set.Set TextStyle
                 , stIndentPara     :: Int
                 , stInDefinition   :: Bool
-                , stTight          :: Bool
                 , stFirstPara      :: Bool
                 , stImageId        :: Int
                 , stTableCaptionId :: Int
                 , stImageCaptionId :: Int
-                , stIdentTypes     :: [(Text,ReferenceType)]
+                , stIdentTypes     :: Map.Map Text ReferenceType
                 , stDirection      :: Maybe Direction
                   -- ^ active writing mode
-                , stDirStyles      :: Map.Map (Text, Direction) Text
-                  -- ^ cache of direction-adjusted paragraph styles,
-                  -- keyed on (parent style, writing mode)
                 }
 
 defaultWriterState :: WriterState
 defaultWriterState =
-    WriterState { stNotes          = []
+    WriterState { stNoteCount      = 0
                 , stTableStyles    = []
+                , stTableStyleCount = 0
                 , stParaStyles     = []
+                , stParaStyleCount = 0
+                , stParaStyleCache = Map.empty
                 , stListOverrides  = Map.empty
                 , stTextStyles     = Map.empty
                 , stTextStyleAttr  = Set.empty
                 , stIndentPara     = 0
                 , stInDefinition   = False
-                , stTight          = False
                 , stFirstPara      = False
                 , stImageId        = 1
                 , stTableCaptionId = 1
                 , stImageCaptionId = 1
-                , stIdentTypes     = []
+                , stIdentTypes     = Map.empty
                 , stDirection      = Nothing
-                , stDirStyles      = Map.empty
                 }
 
-when :: Bool -> Doc Text -> Doc Text
-when p a = if p then a else empty
-
 addTableStyle :: PandocMonad m => Doc Text -> OD m ()
-addTableStyle i = modify $ \s -> s { stTableStyles = i : stTableStyles s }
-
-addNote :: PandocMonad m => Doc Text -> OD m ()
-addNote i = modify $ \s -> s { stNotes = i : stNotes s }
+addTableStyle i = modify $ \s ->
+  s { stTableStyles = i : stTableStyles s
+    , stTableStyleCount = stTableStyleCount s + 1 }
 
 addParaStyle :: PandocMonad m => Doc Text -> OD m ()
-addParaStyle i = modify $ \s -> s { stParaStyles = i : stParaStyles s }
+addParaStyle i = modify $ \s ->
+  s { stParaStyles = i : stParaStyles s
+    , stParaStyleCount = stParaStyleCount s + 1 }
 
 addTextStyle :: PandocMonad m
-             => Set.Set TextStyle -> (Text, Doc Text) -> OD m ()
+             => Set.Set TextStyle -> (Int, Doc Text) -> OD m ()
 addTextStyle attrs i = modify $ \s ->
   s { stTextStyles = Map.insert attrs i (stTextStyles s) }
 
@@ -216,11 +216,13 @@
        Nothing -> do
          styles <- gets stTextStyles
          case Map.lookup at styles of
-              Just (styleName, _) -> return $
-                inTags False "text:span" [("text:style-name",styleName)] d
+              Just (num, _) -> return $
+                inTags False "text:span"
+                  [("text:style-name", textStyleName num)] d
               Nothing -> do
-                let styleName = "T" <> tshow (Map.size styles + 1)
-                addTextStyle at (styleName,
+                let num = Map.size styles + 1
+                    styleName = textStyleName num
+                addTextStyle at (num,
                        inTags False "style:style"
                          [("style:name", styleName)
                          ,("style:family", "text")]
@@ -230,6 +232,10 @@
                 return $ inTags False
                     "text:span" [("text:style-name",styleName)] d
 
+-- | Name of the @n@th automatic text style.
+textStyleName :: Int -> Text
+textStyleName num = "T" <> tshow num
+
 formulaStyles :: [Doc Text]
 formulaStyles = [formulaStyle InlineMath, formulaStyle DisplayMath]
 
@@ -274,16 +280,23 @@
 handleSpaces :: Text -> Doc Text
 handleSpaces s = case T.uncons s of
   Just (' ', _) -> genTag s
-  Just ('\t',x) -> selfClosingTag "text:tab" [] <> rm x
+  Just ('\t',x) -> tabTag <> rm x
   _             -> rm s
   where
-    genTag = T.span (==' ') >>> tag . T.length *** rm >>> uncurry (<>)
-    tag n  = when (n /= 0) $ selfClosingTag "text:s" [("text:c", tshow n)]
-    rm t   = case T.uncons t of
-      Just ( ' ',xs) -> char ' ' <> genTag xs
-      Just ('\t',xs) -> selfClosingTag "text:tab" [] <> genTag xs
-      Just (   x,xs) -> char x <> rm xs
-      Nothing        -> empty
+    tabTag = selfClosingTag "text:tab" []
+    -- collapse a run of spaces into a single <text:s>
+    genTag t = let (spaces, t') = T.span (== ' ') t
+               in  tag (T.length spaces) <> rm t'
+    tag 0  = empty
+    tag n  = selfClosingTag "text:s" [("text:c", tshow n)]
+    -- emit text verbatim up to the next space or tab; a lone space is
+    -- kept literal and any further ones become a <text:s>
+    rm t = let (chunk, t') = T.break (\c -> c == ' ' || c == '\t') t
+               pre = if T.null chunk then empty else literal chunk
+           in  case T.uncons t' of
+                 Just (' ', xs) -> pre <> char ' ' <> genTag xs
+                 Just (_  , xs) -> pre <> tabTag <> genTag xs
+                 Nothing        -> pre
 
 -- | Convert Pandoc document to string in OpenDocument format.
 writeOpenDocument :: PandocMonad m => WriterOptions -> Pandoc -> m Text
@@ -292,12 +305,13 @@
   let colwidth = if writerWrapText opts == WrapAuto
                     then Just $ writerColumns opts
                     else Nothing
-  let meta' = case lookupMetaBlocks "abstract" meta of
-                [] -> meta
-                xs -> B.setMeta "abstract"
-                        (B.divWith ("",[],[("custom-style","Abstract")])
-                          (B.fromList xs))
-                        meta
+  -- The abstract is the one template field that takes block-level
+  -- content, so it is rendered separately; everything else in the
+  -- metadata goes in a context where only inlines are allowed.
+  let abstractBlocks = case lookupMetaBlocks "abstract" meta of
+                         [] -> []
+                         xs -> [Div ("",[],[("custom-style","Abstract")]) xs]
+  let meta' = B.deleteMeta "abstract" meta
   -- Set the default writing direction from the "dir" metadata field;
   -- in its absence, a right-to-left main language implies RTL.
   let mbDir = case lookupMetaString "dir" meta of
@@ -307,19 +321,30 @@
                            Just l | Right lang <- parseLang l
                                   , isRTLLang lang -> Just RTL
                            _ -> Nothing
-  ((body, metadata),s) <- flip runStateT
+  ((body, abstract, metadata),s) <- flip runStateT
         defaultWriterState{ stDirection = mbDir } $ do
-           let collectBlockIdent (Header _ (ident,_,_) _)      = [(ident,HeaderRef)]
-               collectBlockIdent (Figure (ident,_,_) _ _ )     = [(ident,FigureRef)]
-               collectBlockIdent (Table (ident,_,_) _ _ _ _ _) = [(ident,TableRef)]
-               collectBlockIdent _                             = []
-           modify $ \s -> s{ stIdentTypes = query collectBlockIdent blocks }
+           let collectBlockIdent (Header _ attr _)      = identRef HeaderRef attr
+               collectBlockIdent (Figure attr _ _ )     = identRef FigureRef attr
+               collectBlockIdent (Table attr _ _ _ _ _) = identRef TableRef attr
+               collectBlockIdent _                      = []
+               identRef ty (ident,_,_)
+                 | T.null ident = []
+                 | otherwise    = [(ident, ty)]
+           -- only needed to resolve cross references
+           when (isEnabled Ext_xrefs_name opts ||
+                 isEnabled Ext_xrefs_number opts) $
+             modify $ \s -> s{ stIdentTypes =
+                                 Map.fromListWith (\_ old -> old)
+                                   (query collectBlockIdent blocks) }
            m <- metaToContext opts
                   (inlinesToOpenDocument opts . blocksToInlines)
                   (fmap chomp . inlinesToOpenDocument opts)
                   meta'
+           a <- blocksToOpenDocument opts abstractBlocks
            b <- blocksToOpenDocument opts blocks
-           return (b, m)
+           return (b, a, m)
+  -- 'styles' is reversed below, so the text styles are put in
+  -- descending order here to come out as T1, T2, ..., T10
   let styles   = stTableStyles s ++ stParaStyles s ++ formulaStyles ++
                      map snd (sortBy (comparing (Down . fst)) (
                         Map.elems (stTextStyles s)))
@@ -329,6 +354,9 @@
               . defField "toc" (writerTableOfContents opts)
               . defField "toc-depth" (tshow $ writerTOCDepth opts)
               . defField "automatic-styles" automaticStyles
+              . (if null abstractBlocks
+                    then id
+                    else defField "abstract" abstract)
               $ metadata
   return $ render colwidth $
     case writerTemplate opts of
@@ -339,12 +367,15 @@
                    => WriterOptions -> Text -> [Block] -> OD m (Doc Text)
 withParagraphStyle o s bs = do
   s' <- dirStyleFor s
+  -- 'Plain' is treated like 'Para' so that the requested style is not
+  -- lost (e.g. for metadata fields, figure bodies, or tight list items)
   let go (b:bs')
-        | Para l <- b = cont bs' =<<
-            inParagraphTagsWithStyle s' <$> inlinesToOpenDocument o l
-        | otherwise   = cont bs' =<< blockToOpenDocument o b
+        | Para  l <- b = cont bs' =<< styled l
+        | Plain l <- b = cont bs' =<< styled l
+        | otherwise    = cont bs' =<< blockToOpenDocument o b
       go [] = return empty
       cont bs' i = (<>) i <$> go bs'
+      styled l = inParagraphTagsWithStyle s' <$> inlinesToOpenDocument o l
   go bs
 
 inPreformattedTags :: [Doc Text] -> Doc Text
@@ -517,7 +548,7 @@
     HorizontalRule   -> setFirstPara >> return (selfClosingTag "text:p"
                          [ ("text:style-name", "Horizontal_20_Line") ])
     b@(RawBlock f s) -> if f == Format "opendocument"
-                        then return $ text $ T.unpack s
+                        then return $ literal s
                         else empty <$ report (BlockNotRendered b)
     Figure a capt b  -> figure o a capt b
     where
@@ -550,9 +581,9 @@
       table :: PandocMonad m => WriterOptions -> Ann.Table -> OD m (Doc Text)
       table opts
           (Ann.Table (ident, _, _) (Caption _ c) colspecs thead tbodies tfoot) = do
-        tn <- length <$> gets stTableStyles
-        pn <- length <$> gets stParaStyles
-        let  genIds      = map chr [65..]
+        tn <- gets stTableStyleCount
+        pn <- gets stParaStyleCount
+        let  genIds      = map colLetters [0..]
              name        = "Table" <> tshow (tn + 1)
              (aligns, mwidths) = unzip colspecs
              fromWidth (ColWidth w) | w > 0 = w
@@ -560,7 +591,7 @@
              widths = map fromWidth mwidths
              textWidth   = sum widths
              columnIds   = zip genIds widths
-             mkColumn  n = selfClosingTag "table:table-column" [("table:style-name", name <> "." <> T.singleton (fst n))]
+             mkColumn  n = selfClosingTag "table:table-column" [("table:style-name", name <> "." <> fst n)]
              columns     = map mkColumn columnIds
              paraHStyles = paraTableStyles "Heading"  pn aligns
              paraStyles  = paraTableStyles "Contents" (pn + length (newPara paraHStyles)) aligns
@@ -589,8 +620,7 @@
           [] ->
             withParagraphStyle o "Figure" body
           caption -> do
-            imageDoc <- withParagraphStyle o "FigureWithCaption" $
-                        map (\case {Plain i -> Para i; b -> b}) body
+            imageDoc <- withParagraphStyle o "FigureWithCaption" body
             captionDoc <- inlinesToOpenDocument o caption >>=
                           if isEnabled Ext_native_numbering o
                           then numberedFigureCaption ident
@@ -619,7 +649,7 @@
 
 numberedCaption :: Text -> Text -> Text -> Int -> Text -> Doc Text -> Doc Text
 numberedCaption style term name num ident caption =
-    let t = text $ T.unpack term
+    let t = literal term
         r = num - 1
         ident' = case ident of
           "" -> "ref" <> name <> tshow r
@@ -776,7 +806,7 @@
                          inlinesToOpenDocument o
     Cite      _ l -> inlinesToOpenDocument o l
     RawInline f s -> if f == Format "opendocument"
-                       then return $ text $ T.unpack s
+                       then return $ literal s
                        else do
                          report $ InlineNotRendered ils
                          return empty
@@ -825,16 +855,17 @@
           then i
           else fmap mkBookmarkedSpan i
       mkNote     l = do
-        n <- length <$> gets stNotes
+        n <- gets stNoteCount
+        -- bump the counter before rendering the body, so that a note
+        -- nested inside this one does not reuse the same text:id
+        modify $ \st -> st{ stNoteCount = n + 1 }
         let footNote t = inTags False "text:note"
                          [ ("text:id"        , "ftn" <> tshow n)
                          , ("text:note-class", "footnote"     )] $
                          inTagsSimple "text:note-citation" (text . show $ n + 1) <>
                          inTagsSimple "text:note-body" t
-        nn <- footNote <$> withAlteredTextStyles (const mempty)
-                            (withParagraphStyle o "Footnote" l)
-        addNote nn
-        return nn
+        footNote <$> withAlteredTextStyles (const mempty)
+                       (withParagraphStyle o "Footnote" l)
 
 formatOpenDocument :: FormatOptions -> [SourceLine] -> [[Doc Text]]
 formatOpenDocument _fmtOpts = map (map toHlTok)
@@ -845,12 +876,10 @@
 preformatted :: Text -> Doc Text
 preformatted s = handleSpaces $ escapeStringForXML s
 
-mkLink :: WriterOptions -> [(Text,ReferenceType)] -> Text -> Text -> Doc Text -> Doc Text
+mkLink :: WriterOptions -> Map.Map Text ReferenceType -> Text -> Text
+       -> Doc Text -> Doc Text
 mkLink o identTypes s t d =
-  let maybeIdentAndType = case T.uncons s of
-                            Just ('#', ident) -> find ((ident ==) . fst) identTypes
-                            _                 -> Nothing
-      d' = inSpanTags "Definition" d
+  let d' = inSpanTags "Definition" d
       ref refType format ident       = inTags False refType
                                        [ ("text:reference-format", format ),
                                          ("text:ref-name", ident) ]
@@ -875,16 +904,27 @@
                                           , ("xlink:href" , s       )
                                           , ("office:name", t       )
                                           ] d'
-      linkOrReference = case maybeIdentAndType of
-                          Just (ident, HeaderRef) -> bookmarkRef' ident
-                          Just (ident, TableRef)  -> sequenceRef' ident
-                          Just (ident, FigureRef) -> sequenceRef' ident
-                          _                       -> link
+      linkOrReference = case T.uncons s of
+                          Just ('#', ident) ->
+                            case Map.lookup ident identTypes of
+                              Just HeaderRef -> bookmarkRef' ident
+                              Just TableRef  -> sequenceRef' ident
+                              Just FigureRef -> sequenceRef' ident
+                              Nothing        -> link
+                          _ -> link
       in if isEnabled Ext_xrefs_name o || isEnabled Ext_xrefs_number o
             then linkOrReference
             else link
 
-tableStyle :: Int -> Double -> [(Char,Double)] -> Doc Text
+-- | Spreadsheet-style column names (@A@, @B@, ..., @Z@, @AA@, @AB@, ...).
+-- Plain @chr@ arithmetic would run past @Z@ into characters like @[@
+-- and @\\@, which are not legal in a @style:name@.
+colLetters :: Int -> Text
+colLetters n
+  | n < 26    = T.singleton (chr (65 + n))
+  | otherwise = colLetters (n `div` 26 - 1) <> T.singleton (chr (65 + n `mod` 26))
+
+tableStyle :: Int -> Double -> [(Text,Double)] -> Doc Text
 tableStyle num textWidth wcs =
     let tableId        = "Table" <> tshow (num + 1)
         tableWidthAttr :: [(Text,Text)]
@@ -898,10 +938,10 @@
                          selfClosingTag "style:table-properties"
                          (("table:align", "center") : tableWidthAttr)
         colStyle (c,0) = selfClosingTag "style:style"
-                         [ ("style:name"  , tableId <> "." <> T.singleton c)
+                         [ ("style:name"  , tableId <> "." <> c)
                          , ("style:family", "table-column"       )]
         colStyle (c,w) = inTags True "style:style"
-                         [ ("style:name"  , tableId <> "." <> T.singleton c)
+                         [ ("style:name"  , tableId <> "." <> c)
                          , ("style:family", "table-column"       )] $
                          selfClosingTag "style:table-column-properties"
                          [("style:rel-column-width", T.pack $ printf "%d*" (floor $ w * 65535 :: Integer))]
@@ -930,32 +970,18 @@
 paraStyle attrs = do
   i  <- (*) (0.5 :: Double) . fromIntegral <$> gets stIndentPara
   b  <- gets stInDefinition
-  t  <- gets stTight
   dirAttrs <- getDirAttrs
   let indentVal = flip (<>) "in" . tshow $ if b then max 0.5 i else i
-      tight     = if t then [ ("fo:margin-top"          , "0in"    )
-                            , ("fo:margin-bottom"       , "0in"    )]
-                       else []
       indent    = if i /= 0 || b
                       then [ ("fo:margin-left"         , indentVal)
                            , ("fo:margin-right"        , "0in"    )
                            , ("fo:text-indent"         , "0in"    )
                            , ("style:auto-text-indent" , "false"  )]
                       else []
-      attributes = indent <> tight <> dirAttrs
+      attributes = indent <> dirAttrs
   case (attributes, attrs) of
     ([], [("style:parent-style-name", parent)]) -> return parent
-    _ -> do
-      pn <- (+) 1 . length <$> gets stParaStyles
-      let name      = "P" <> tshow pn
-          styleAttr = [ ("style:name"  , name)
-                      , ("style:family", "paragraph") ]
-          paraProps = if null attributes
-                         then mempty
-                         else selfClosingTag
-                                 "style:paragraph-properties" attributes
-      addParaStyle $ inTags True "style:style" (styleAttr <> attrs) paraProps
-      return name
+    _ -> mkParaStyle attrs attributes
 
 paraStyleFromParent :: PandocMonad m => Text -> [(Text,Text)] -> OD m Text
 paraStyleFromParent parent attrs = do
@@ -963,14 +989,29 @@
   let attrs' = attrs <> dirAttrs
   if null attrs'
      then return parent
-     else do
-      pn <- (+) 1 . length <$> gets stParaStyles
-      let name      = "P" <> tshow pn
-          styleAttr = [ ("style:name"             , name)
-                      , ("style:family"           , "paragraph")
-                      , ("style:parent-style-name", parent)]
-          paraProps = selfClosingTag "style:paragraph-properties" attrs'
+     else mkParaStyle [("style:parent-style-name", parent)] attrs'
+
+-- | Create an automatic paragraph style with the given @style:style@
+-- attributes and @style:paragraph-properties@, reusing an identical
+-- style if one has already been created.
+mkParaStyle :: PandocMonad m => [(Text,Text)] -> [(Text,Text)] -> OD m Text
+mkParaStyle attrs props = do
+  cache <- gets stParaStyleCache
+  case Map.lookup (attrs, props) cache of
+    Just name -> return name
+    Nothing -> do
+      name <- ("P" <>) . tshow . (+ 1) <$> gets stParaStyleCount
+      let styleAttr = ("style:name"  , name)
+                    : ("style:family", "paragraph")
+                    : attrs
+          paraProps = if null props
+                         then mempty
+                         else selfClosingTag
+                                 "style:paragraph-properties" props
       addParaStyle $ inTags True "style:style" styleAttr paraProps
+      modify $ \st -> st{ stParaStyleCache =
+                            Map.insert (attrs, props) name
+                                       (stParaStyleCache st) }
       return name
 
 getDirAttrs :: PandocMonad m => OD m [(Text, Text)]
@@ -986,26 +1027,14 @@
 
 -- | Adjust a named paragraph style for the current writing direction.
 -- When a direction is active, an automatic style derived from the
--- given style with the appropriate @style:writing-mode@ is created
--- (and cached).  Automatic style names (@P1@, @P2@, ...) pass through
--- unchanged, since automatic styles are always created with the
--- current direction included.
+-- given style with the appropriate @style:writing-mode@ is created.
+-- With no direction active the name passes through unchanged, as do
+-- automatic style names (@P1@, @P2@, ...), since automatic styles are
+-- always created with the current direction included.
 dirStyleFor :: PandocMonad m => Text -> OD m Text
-dirStyleFor parent = do
-  mbDir <- gets stDirection
-  case mbDir of
-    Nothing -> return parent
-    Just d
-      | isAutoStyleName parent -> return parent
-      | otherwise -> do
-          cache <- gets stDirStyles
-          case Map.lookup (parent, d) cache of
-            Just name -> return name
-            Nothing -> do
-              name <- paraStyleFromParent parent []
-              modify $ \st -> st{ stDirStyles =
-                     Map.insert (parent, d) name (stDirStyles st) }
-              return name
+dirStyleFor parent
+  | isAutoStyleName parent = return parent
+  | otherwise              = paraStyleFromParent parent []
   where
     isAutoStyleName t = case T.uncons t of
       Just ('P', ds) -> not (T.null ds) && T.all isDigit ds
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
@@ -28,12 +28,13 @@
 import Data.Bifunctor (first, second)
 import Network.URI (unEscapeString)
 import qualified Data.Text as T
-import Control.Monad (unless)
+import Control.Monad (unless, when)
 import Control.Monad.State ( StateT, evalStateT, gets, modify )
 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)
@@ -48,6 +49,7 @@
 import qualified Text.Pandoc.UTF8 as UTF8
 import Text.Collate.Lang (Lang(..), parseLang)
 import Text.Printf (printf)
+import Text.Read (readMaybe)
 import Data.Char (isDigit)
 import Data.Maybe (fromMaybe)
 import Unicode.Char (isXIDContinue)
@@ -59,7 +61,11 @@
   evalStateT (pandocToTypst options document)
     WriterState{ stOptions = options,
                  stEscapeContext = NormalContext,
-                 stHighlighting = False }
+                 stHighlighting = False,
+                 stCsl = False,
+                 stCslHangingIndent = Nothing,
+                 stCslEntrySpacing = Nothing
+               }
 
 data EscapeContext = NormalContext | TermContext
   deriving (Show, Eq)
@@ -68,7 +74,10 @@
   WriterState {
     stOptions :: WriterOptions,
     stEscapeContext :: EscapeContext,
-    stHighlighting :: Bool
+    stHighlighting :: Bool,
+    stCsl :: Bool,
+    stCslHangingIndent :: Maybe Text,
+    stCslEntrySpacing :: Maybe Text
     }
 
 type TW m = StateT WriterState m
@@ -94,7 +103,9 @@
                            _         -> [])
                   $ lookupMetaInlines "nocite" meta
   hasHighlighting <- gets stHighlighting
-
+  hasCslSettings <- gets stCsl
+  cslHangingIndent <- gets stCslHangingIndent
+  cslEntrySpacing <- gets stCslEntrySpacing
   let context = defField "body" main
               $ defField "toc" (writerTableOfContents options)
               $ (if isEnabled Ext_citations options
@@ -111,6 +122,9 @@
                           resetField "lang" (langLanguage l) .
                           maybe id (resetField "region") (langRegion l))
               $ defField "csl" (lookupMetaString "citation-style" meta) -- #10661
+              $ defField "csl-settings" hasCslSettings
+              $ maybe id (defField "csl-hanging-indent") cslHangingIndent
+              $ maybe id (defField "csl-entry-spacing") cslEntrySpacing
               $ defField "smart" (isEnabled Ext_smart options)
               $ defField "abstract-title" abstractTitle
               $ defField "toc-depth" (tshow $ writerTOCDepth options)
@@ -362,17 +376,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
@@ -394,11 +409,22 @@
                           $$ ")" $$ lab $$ blankline
     Div (ident,_,_) (Header lev ("",cls,kvs) ils:rest) ->
       blocksToTypst (Header lev (ident,cls,kvs) ils:rest)
-    Div (ident,_,kvs) blocks -> do
+    Div (ident,cls,kvs) blocks -> do
       let lab = case lookup "typst-label" kvs of
                   Just l -> toLabel FreestandingLabel l
                   Nothing -> toLabel FreestandingLabel ident
       let (typstAttrs,typstTextAttrs) = pickTypstAttrs kvs
+      when ("csl-bib-body" `elem` cls) $
+        modify $ \st -> st{ stCsl = True
+                          , stCslEntrySpacing =
+                              case lookup "entry-spacing" kvs >>=
+                                     readMaybe . T.unpack of
+                                Just (n :: Int) -> Just $ tshow n <> "em"
+                                Nothing -> Nothing
+                          , stCslHangingIndent =
+                              if "hanging-indent" `elem` cls
+                                 then Just "1.5em"
+                                 else Nothing }
       contents <- blocksToTypst blocks
       return $ "#block" <> toTypstPropsListParens typstAttrs <> "["
         $$ toTypstPoundSetText typstTextAttrs
@@ -598,7 +624,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/XML.hs b/src/Text/Pandoc/XML.hs
--- a/src/Text/Pandoc/XML.hs
+++ b/src/Text/Pandoc/XML.hs
@@ -32,7 +32,7 @@
 import Commonmark.Entity (lookupEntity)
 import Text.HTML.TagSoup.Entity (htmlEntities)
 import Text.DocLayout
-    ( ($$), char, hcat, nest, text, Doc, HasChars )
+    ( ($$), nest, text, Doc, HasChars )
 import Text.Printf (printf)
 import qualified Data.Map as M
 import Data.String ( IsString )
@@ -47,35 +47,63 @@
                        '"' -> "&quot;"
                        c   -> T.singleton c
 
+-- | Is this character legal in XML at all?
+-- See <https://www.w3.org/TR/xml/#charsets>.
+isLegalXMLChar :: Char -> Bool
+isLegalXMLChar c = c == '\t' || c == '\n' || c == '\r' ||
+                   (c >= '\x20' && c <= '\xD7FF') ||
+                   (c >= '\xE000' && c <= '\xFFFD') ||
+                   (c >= '\x10000' && c <= '\x10FFFF')
+
+-- | Does this character need to be replaced or dropped by
+-- 'escapeStringForXML'?
+isSpecialXMLChar :: Char -> Bool
+isSpecialXMLChar = \case
+  '&' -> True
+  '<' -> True
+  '>' -> True
+  '"' -> True
+  c   -> not (isLegalXMLChar c)
+
 -- | Escape string as needed for XML.  Entity references are not preserved.
 escapeStringForXML :: Text -> Text
-escapeStringForXML = T.concatMap escapeCharForXML . T.filter isLegalXMLChar
-  where isLegalXMLChar c = c == '\t' || c == '\n' || c == '\r' ||
-                           (c >= '\x20' && c <= '\xD7FF') ||
-                           (c >= '\xE000' && c <= '\xFFFD') ||
-                           (c >= '\x10000' && c <= '\x10FFFF')
-  -- see https://www.w3.org/TR/xml/#charsets
+escapeStringForXML t
+  | T.null rest = t  -- nothing to do, so return the input without copying
+  | otherwise   = T.concat (chunk : go rest)
+ where
+  (chunk, rest) = T.break isSpecialXMLChar t
+  -- 'go' is always given text starting with a special character.  Text
+  -- between special characters is copied in runs rather than character
+  -- by character.
+  go t' = let (specials, t'') = T.span isSpecialXMLChar t'
+              (chunk', rest') = T.break isSpecialXMLChar t''
+           in T.concatMap escape specials :
+                if T.null rest' then [chunk'] else chunk' : go rest'
+  -- a special character is either one of & < > " or else not
+  -- representable in XML, in which case it is dropped
+  escape c | isLegalXMLChar c = escapeCharForXML c
+           | otherwise        = T.empty
 
 -- | Escape newline characters as &#10;
 escapeNls :: Text -> Text
-escapeNls = T.concatMap $ \case
-  '\n' -> "&#10;"
-  c    -> T.singleton c
+escapeNls t
+  | T.elem '\n' t = T.replace "\n" "&#10;" t
+  | otherwise     = t
 
--- | Return a text object with a string of formatted XML attributes.
-attributeList :: (HasChars a, IsString a) => [(Text, Text)] -> Doc a
-attributeList = hcat . map
-  (\(a, b) -> text (T.unpack $ " " <> escapeStringForXML a <> "=\"" <>
-  escapeNls (escapeStringForXML b) <> "\""))
+-- | The pieces of a string of formatted XML attributes, each attribute
+-- preceded by a space.
+attributeList :: [(Text, Text)] -> [Text]
+attributeList = concatMap
+  (\(a, b) -> [" ", escapeStringForXML a, "=\"",
+               escapeNls (escapeStringForXML b), "\""])
 
 -- | Put the supplied contents between start and end tags of tagType,
 --   with specified attributes and (if specified) indentation.
 inTags :: (HasChars a, IsString a)
       => Bool -> Text -> [(Text, Text)] -> Doc a -> Doc a
 inTags isIndented tagType attribs contents =
-  let openTag = char '<' <> text (T.unpack tagType) <> attributeList attribs <>
-                char '>'
-      closeTag  = text "</" <> text (T.unpack tagType) <> char '>'
+  let openTag  = tagDoc ("<" : tagType : attributeList attribs ++ [">"])
+      closeTag = tagDoc ["</", tagType, ">"]
   in  if isIndented
          then openTag $$ nest 2 contents $$ closeTag
          else openTag <> contents <> closeTag
@@ -84,7 +112,13 @@
 selfClosingTag :: (HasChars a, IsString a)
                => Text -> [(Text, Text)] -> Doc a
 selfClosingTag tagType attribs =
-  char '<' <> text (T.unpack tagType) <> attributeList attribs <> text " />"
+  tagDoc ("<" : tagType : attributeList attribs ++ [" />"])
+
+-- | Assemble the pieces of a tag into a single 'Doc', so that a tag
+-- costs one 'Text' allocation and one 'Doc' node rather than one of
+-- each per tag name and per attribute.
+tagDoc :: (HasChars a, IsString a) => [Text] -> Doc a
+tagDoc = text . T.unpack . T.concat
 
 -- | Put the supplied contents between start and end tags of tagType.
 inTagsSimple :: (HasChars a, IsString 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
@@ -112,10 +112,18 @@
             "docx/char_styles.docx"
             "docx/char_styles.native"
           , testCompare
+            "double strikethrough"
+            "docx/double_strikeout.docx"
+            "docx/double_strikeout.native"
+          , testCompare
             "hyperlinks"
             "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"
@@ -421,8 +429,8 @@
             "docx/track_changes_deletion_reject.native"
           , testCompareWithOpts def{readerTrackChanges=AllChanges}
             "keep insertion (all)"
-            "docx/track_changes_deletion.docx"
-            "docx/track_changes_deletion_all.native"
+            "docx/track_changes_insertion.docx"
+            "docx/track_changes_insertion_all.native"
           , testCompareWithOpts def{readerTrackChanges=AllChanges}
             "keep deletion (all)"
             "docx/track_changes_deletion.docx"
@@ -439,6 +447,18 @@
             "move text (all)"
             "docx/track_changes_move.docx"
             "docx/track_changes_move_all.native"
+          , testCompareWithOpts def{readerTrackChanges=AllChanges}
+            "spaces at the edges of changes stay in the span (all)"
+            "docx/track_changes_edge_spaces.docx"
+            "docx/track_changes_edge_spaces_all.native"
+          , testCompareWithOpts def{readerTrackChanges=AcceptChanges}
+            "spaces at the edges of changes (accept)"
+            "docx/track_changes_edge_spaces.docx"
+            "docx/track_changes_edge_spaces_accept.native"
+          , testCompareWithOpts def{readerTrackChanges=RejectChanges}
+            "spaces at the edges of changes (reject)"
+            "docx/track_changes_edge_spaces.docx"
+            "docx/track_changes_edge_spaces_reject.native"
           , testCompareWithOpts def{readerTrackChanges=AcceptChanges}
             "comments (accept -- no comments)"
             "docx/comments.docx"
diff --git a/test/Tests/Readers/DokuWiki.hs b/test/Tests/Readers/DokuWiki.hs
--- a/test/Tests/Readers/DokuWiki.hs
+++ b/test/Tests/Readers/DokuWiki.hs
@@ -32,7 +32,48 @@
 (=:) = test dokuwiki
 
 tests :: [TestTree]
-tests = [ testGroup "inlines"
+tests = [ testGroup "math groups and comments"
+          [ testGroup name
+            [ test reader "nested math in a color box" $
+                wrap "\\colorbox{aqua}{$F=ma$}" =?>
+                  para (makeMath "\\colorbox{aqua}{$F=ma$}")
+            , test reader "unpaired escaped opening brace" $
+                wrap "{\\{x}" =?> para (makeMath "{\\{x}")
+            , test reader "unpaired escaped closing brace" $
+                wrap escapedClosing =?> para (makeMath escapedClosing)
+            , test reader "commented delimiters and braces" $
+                wrap commented =?> para (makeMath commented)
+            , test reader "commented closing brace inside a group" $
+                wrap ("{x% } " <> close <> "\ny}") =?>
+                  para (makeMath $ "{x% } " <> close <> "\ny}")
+            , test reader "commented opening brace inside a group" $
+                wrap "{x% {\ny}" =?> para (makeMath "{x% {\ny}")
+            , test reader "escaped percent is literal" $
+                wrap "x\\%" =?> para (makeMath "x\\%")
+            , test reader "percent after an escaped backslash is a comment" $
+                wrap ("x\\\\% " <> close <> "\ny") =?>
+                  para (makeMath $ "x\\\\% " <> close <> "\ny")
+            ]
+          | (name, ext, open, close, makeMath) <-
+              [ ("dollars", Ext_tex_math_dollars, "$", "$", math)
+              , ("double dollars", Ext_tex_math_dollars,
+                 "$$", "$$", displayMath)
+              , ("parentheses", Ext_tex_math_single_backslash,
+                 "\\(", "\\)", math)
+              , ("brackets", Ext_tex_math_single_backslash,
+                 "\\[", "\\]", displayMath)
+              , ("double-backslash parentheses", Ext_tex_math_double_backslash,
+                 "\\\\(", "\\\\)", math)
+              , ("double-backslash brackets", Ext_tex_math_double_backslash,
+                 "\\\\[", "\\\\]", displayMath)
+              ]
+          , let reader = purely $ readDokuWiki def
+                  { readerExtensions = extensionsFromList [ext] }
+                wrap s = open <> s <> close
+                escapedClosing = "{\\} hi " <> open <> "x" <> close <> " bye}"
+                commented = "x% " <> close <> " { } \\%\ny"
+          ]
+        , testGroup "inlines"
           [ "Bold" =:
             "**bold**" =?>
             para (strong "bold")
diff --git a/test/Tests/Readers/HTML.hs b/test/Tests/Readers/HTML.hs
--- a/test/Tests/Readers/HTML.hs
+++ b/test/Tests/Readers/HTML.hs
@@ -57,7 +57,48 @@
                             { writerWrapText = WrapPreserve })
 
 tests :: [TestTree]
-tests = [ testGroup "base tag"
+tests = [ testGroup "math groups and comments"
+          [ testGroup name
+            [ test reader "nested math in a color box" $
+                wrap "\\colorbox{aqua}{$F=ma$}" =?>
+                  para (makeMath "\\colorbox{aqua}{$F=ma$}")
+            , test reader "unpaired escaped opening brace" $
+                wrap "{\\{x}" =?> para (makeMath "{\\{x}")
+            , test reader "unpaired escaped closing brace" $
+                wrap escapedClosing =?> para (makeMath escapedClosing)
+            , test reader "commented delimiters and braces" $
+                wrap commented =?> para (makeMath commented)
+            , test reader "commented closing brace inside a group" $
+                wrap ("{x% } " <> close <> "\ny}") =?>
+                  para (makeMath $ "{x% } " <> close <> "\ny}")
+            , test reader "commented opening brace inside a group" $
+                wrap "{x% {\ny}" =?> para (makeMath "{x% {\ny}")
+            , test reader "escaped percent is literal" $
+                wrap "x\\%" =?> para (makeMath "x\\%")
+            , test reader "percent after an escaped backslash is a comment" $
+                wrap ("x\\\\% " <> close <> "\ny") =?>
+                  para (makeMath $ "x\\\\% " <> close <> "\ny")
+            ]
+          | (name, ext, open, close, makeMath) <-
+              [ ("dollars", Ext_tex_math_dollars, "$", "$", math)
+              , ("double dollars", Ext_tex_math_dollars,
+                 "$$", "$$", displayMath)
+              , ("parentheses", Ext_tex_math_single_backslash,
+                 "\\(", "\\)", math)
+              , ("brackets", Ext_tex_math_single_backslash,
+                 "\\[", "\\]", displayMath)
+              , ("double-backslash parentheses", Ext_tex_math_double_backslash,
+                 "\\\\(", "\\\\)", math)
+              , ("double-backslash brackets", Ext_tex_math_double_backslash,
+                 "\\\\[", "\\\\]", displayMath)
+              ]
+          , let reader = purely $ readHtml def
+                  { readerExtensions = extensionsFromList [ext] }
+                wrap s = "<p>" <> open <> s <> close <> "</p>"
+                escapedClosing = "{\\} hi " <> open <> "x" <> close <> " bye}"
+                commented = "x% " <> close <> " { } \\%\ny"
+          ]
+        , testGroup "base tag"
           [ test html "simple" $
             "<head><base href=\"http://www.w3schools.com/images/foo\" ></head><body><img src=\"stickman.gif\" alt=\"Stickman\"></head>" =?>
             plain (image "http://www.w3schools.com/images/stickman.gif" "" (text "Stickman"))
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/Markdown.hs b/test/Tests/Readers/Markdown.hs
--- a/test/Tests/Readers/Markdown.hs
+++ b/test/Tests/Readers/Markdown.hs
@@ -162,7 +162,91 @@
 -}
 
 tests :: [TestTree]
-tests = [ testGroup "inline code"
+tests = [ testGroup "math groups"
+          [ testGroup name
+            [ test reader "nested math in text" $
+                wrap "\\text{hi $x$ bye}" =?>
+                  para (makeMath "\\text{hi $x$ bye}")
+            , test reader "nested math in a color box" $
+                wrap "\\colorbox{aqua}{$F=ma$}" =?>
+                  para (makeMath "\\colorbox{aqua}{$F=ma$}")
+            , test reader "nested groups" $
+                wrap "\\fcolorbox{red}{aqua}{\\text{hi $x$ bye}}" =?>
+                  para (makeMath
+                    "\\fcolorbox{red}{aqua}{\\text{hi $x$ bye}}")
+            , test reader "matching delimiters inside a group" $
+                wrap grouped =?> para (makeMath grouped)
+            , test reader "unpaired escaped opening brace inside a group" $
+                wrap "{\\{x}" =?> para (makeMath "{\\{x}")
+            , test reader "unpaired escaped closing brace inside a group" $
+                wrap escapedClosing =?> para (makeMath escapedClosing)
+            , test reader "unpaired escaped opening brace outside a group" $
+                wrap "\\{x" =?> para (makeMath "\\{x")
+            , test reader "unpaired escaped closing brace outside a group" $
+                wrap "x\\}" =?> para (makeMath "x\\}")
+            , test reader "escaped backslash before closing brace" $
+                wrap "{x\\\\}" =?> para (makeMath "{x\\\\}")
+            , test reader "multiline group" $
+                wrap "{a\nb}" =?> para (makeMath "{a\nb}")
+            , test reader "commented delimiters and braces" $
+                wrap commented =?> para (makeMath commented)
+            , test reader "commented closing brace inside a group" $
+                wrap ("{x% } " <> close <> "\ny}") =?>
+                  para (makeMath $ "{x% } " <> close <> "\ny}")
+            , test reader "commented opening brace inside a group" $
+                wrap "{x% {\ny}" =?> para (makeMath "{x% {\ny}")
+            , test reader "escaped percent is literal" $
+                wrap "x\\%" =?> para (makeMath "x\\%")
+            , test reader "escaped percent does not hide a closing brace" $
+                wrap "{x\\%}" =?> para (makeMath "{x\\%}")
+            , test reader "escaped percent does not hide an opening brace" $
+                wrap ("x\\%" <> grouped) =?>
+                  para (makeMath $ "x\\%" <> grouped)
+            , test reader "percent after an escaped backslash starts a comment" $
+                wrap ("x\\\\% " <> close <> "\ny") =?>
+                  para (makeMath $ "x\\\\% " <> close <> "\ny")
+            , test reader "escaped backslash before a comment inside a group" $
+                wrap "{x\\\\% }\ny}" =?>
+                  para (makeMath "{x\\\\% }\ny}")
+            , test reader "comment cannot supply the closing delimiter" $
+                wrap "x%" =?>
+                  para (text $ literalOpen <> "x%" <> literalClose)
+            , test reader "consecutive comments" $
+                wrap ("x%\n% } $ \\" <> "\ny") =?>
+                  para (makeMath $ "x%\n% } $ \\" <> "\ny")
+            , test reader "following math remains separate" $
+                (wrap grouped <> " and " <> wrap "y") =?>
+                  para (makeMath grouped <> " and " <> makeMath "y")
+            , test reader "unclosed group falls back to text" $
+                wrap "{x" =?> para (text $ literalOpen <> "{x" <>
+                                          literalClose)
+            , test reader "unclosed group does not hide later math" $
+                (wrap "{x" <> "\n\n" <> wrap "y") =?>
+                  (para (text $ literalOpen <> "{x" <> literalClose) <>
+                   para (makeMath "y"))
+            ]
+          | (name, ext, open, close, literalOpen, literalClose, makeMath) <-
+              [ ("dollars", Ext_tex_math_dollars,
+                 "$", "$", "$", "$", math)
+              , ("double dollars", Ext_tex_math_dollars,
+                 "$$", "$$", "$$", "$$", displayMath)
+              , ("parentheses", Ext_tex_math_single_backslash,
+                 "\\(", "\\)", "(", ")", math)
+              , ("brackets", Ext_tex_math_single_backslash,
+                 "\\[", "\\]", "[", "]", displayMath)
+              , ("double-backslash parentheses", Ext_tex_math_double_backslash,
+                 "\\\\(", "\\\\)", "\\(", "\\)", math)
+              , ("double-backslash brackets", Ext_tex_math_double_backslash,
+                 "\\\\[", "\\\\]", "\\[", "\\]", displayMath)
+              ]
+          , let reader = purely $ readMarkdown def
+                  { readerExtensions = extensionsFromList [ext] }
+                wrap s = open <> s <> close
+                grouped = "\\text{hi " <> open <> "x" <> close <> " bye}"
+                escapedClosing = "{\\} hi " <> open <> "x" <> close <> " bye}"
+                commented = "x% " <> close <> " { } \\%\ny"
+          ]
+        , testGroup "inline code"
           [ "with attribute" =:
             "`document.write(\"Hello\");`{.javascript}"
             =?> para
diff --git a/test/Tests/Readers/ODT.hs b/test/Tests/Readers/ODT.hs
--- a/test/Tests/Readers/ODT.hs
+++ b/test/Tests/Readers/ODT.hs
@@ -28,7 +28,10 @@
 defopts = def{ readerExtensions = getDefaultExtensions "odt" }
 
 tests :: [TestTree]
-tests = testsComparingToMarkdown ++ testsComparingToNative
+tests = testsComparingToMarkdown ++ testsComparingToNative ++
+        [ testGroup "fodt" (fodtTestsComparingToMarkdown ++
+                            fodtTestsComparingToNative ++
+                            fodtTestsComparingToOwnNative) ]
 
 testsComparingToMarkdown :: [TestTree]
 testsComparingToMarkdown    = map nameToTest namesOfTestsComparingToMarkdown
@@ -50,7 +53,73 @@
         toODTPath      name = "odt/odt/"      ++ name ++ ".odt"
         toNativePath   name = "odt/native/"   ++ name ++ ".native"
 
+-- The flat files are LibreOffice's conversions of the zipped files, and are
+-- compared against the very same expectations, so that flat and zipped input
+-- are asserted to converge on identical documents.  office:settings,
+-- office:scripts and office:master-styles have been removed from them: they
+-- are view state that the reader never looks at, and they were a quarter of
+-- the bytes.
+--
+-- Four fixtures are enough, because a flat file shares nearly all of the
+-- reader with a zipped one: once readFODT has handed the root element over,
+-- the converter is the very same code.  Only two paths are reached by a flat
+-- file alone, and each has a fixture that reaches it:
+--
+--   * "image"   -- an image with no xlink:href, so the data must be decoded
+--                  from office:binary-data and the type taken from
+--                  draw:mime-type rather than found in the archive
+--   * "formula" -- a draw:object with no xlink:href, so the MathML is read
+--                  from the element itself rather than from a sub-document
+--
+-- The other two guard against whitespace, which is the one hazard that is
+-- peculiar to this format without being a separate branch of the reader:
+-- LibreOffice pretty-prints a flat file, and that indentation is in the XML
+-- the reader sees.  It has already been read as content once, as the alt text
+-- of an image.  "preformattedText" is a CodeBlock, whose whitespace is
+-- significant and therefore compared exactly, and "textMixedStyles" is a run
+-- of nested emphasis, where stray indentation would show up between inlines.
+--
+-- Three of the zipped fixtures *cannot* be used, rather than merely being
+-- surplus: LibreOffice's flat export renames the automatic style that marks
+-- inlined code (inlinedCode), drops the cached text of a chapter reference
+-- field (referenceToChapter), and resolves a relative reference to a
+-- non-embedded image against the local filesystem (imageRelative).
 
+fodtTestsComparingToMarkdown :: [TestTree]
+fodtTestsComparingToMarkdown = map nameToTest namesOfFODTTestsComparingToMarkdown
+  where nameToTest     name = createTest
+                                compareFODTToMarkdown
+                                name
+                                (toFODTPath     name)
+                                (toMarkdownPath name)
+        toFODTPath     name = "odt/fodt/"     ++ name ++ ".fodt"
+        toMarkdownPath name = "odt/markdown/" ++ name ++ ".md"
+
+fodtTestsComparingToNative :: [TestTree]
+fodtTestsComparingToNative = map nameToTest namesOfFODTTestsComparingToNative
+  where nameToTest     name = createTest
+                                compareFODTToNative
+                                name
+                                (toFODTPath     name)
+                                (toNativePath   name)
+        toFODTPath     name = "odt/fodt/"     ++ name ++ ".fodt"
+        toNativePath   name = "odt/native/"   ++ name ++ ".native"
+
+-- Images need their own expectations, because the media bag paths are
+-- pandoc's own when the image data is embedded, and because LibreOffice
+-- converts the frame dimensions to inches on export.
+
+fodtTestsComparingToOwnNative :: [TestTree]
+fodtTestsComparingToOwnNative = map nameToTest namesOfFODTTestsComparingToOwnNative
+  where nameToTest     name = createTest
+                                compareFODTToNative
+                                name
+                                (toFODTPath     name)
+                                (toNativePath   name)
+        toFODTPath     name = "odt/fodt/"        ++ name ++ ".fodt"
+        toNativePath   name = "odt/fodt-native/" ++ name ++ ".native"
+
+
 newtype NoNormPandoc = NoNormPandoc {unNoNorm :: Pandoc}
   deriving ( Show )
 
@@ -91,7 +160,25 @@
    odt          <- getNoNormVia id "odt"      <$> runIO (readODT      opts odtFile)
    return (odt,markdown)
 
+compareFODTToNative :: TestCreator
+compareFODTToNative opts fodtPath nativePath = do
+   nativeFile   <- UTF8.toText <$> BS.readFile nativePath
+   fodtFile     <- UTF8.toText <$> BS.readFile fodtPath
+   native       <- getNoNormVia id "native" <$> runIO (readNative def nativeFile)
+   fodt         <- getNoNormVia id "fodt"   <$> runIO (readFODT opts fodtFile)
+   return (fodt,native)
 
+compareFODTToMarkdown :: TestCreator
+compareFODTToMarkdown opts fodtPath markdownPath = do
+   markdownFile <- UTF8.toText <$> BS.readFile markdownPath
+   fodtFile     <- UTF8.toText <$> BS.readFile fodtPath
+   markdown     <- getNoNormVia id "markdown" <$>
+                      runIO (readMarkdown def{ readerExtensions = pandocExtensions }
+                              markdownFile)
+   fodt         <- getNoNormVia id "fodt"     <$> runIO (readFODT opts fodtFile)
+   return (fodt,markdown)
+
+
 createTest :: TestCreator
            -> TestName
            -> FilePath -> FilePath
@@ -194,3 +281,14 @@
                                   , "unorderedList"
                                   , "unorderedListHeader"
                                   ]
+
+namesOfFODTTestsComparingToMarkdown :: [ String ]
+namesOfFODTTestsComparingToMarkdown = [ "formula" ]
+
+namesOfFODTTestsComparingToNative :: [ String ]
+namesOfFODTTestsComparingToNative = [ "preformattedText"
+                                    , "textMixedStyles"
+                                    ]
+
+namesOfFODTTestsComparingToOwnNative :: [ String ]
+namesOfFODTTestsComparingToOwnNative = [ "image" ]
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 =
@@ -108,6 +124,11 @@
             "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
             "docx/lists_continuing.native"
@@ -197,6 +218,11 @@
             def
             "docx/raw-bookmarks.native"
             "docx/golden/raw-bookmarks.docx"
+          , docxTest
+            "mark math"
+            def
+            "docx/mark_math.native"
+            "docx/golden/mark_math.docx"
           ]
         , testGroup "track changes"
           [ docxTest
diff --git a/test/Tests/Writers/LaTeX.hs b/test/Tests/Writers/LaTeX.hs
--- a/test/Tests/Writers/LaTeX.hs
+++ b/test/Tests/Writers/LaTeX.hs
@@ -70,7 +70,7 @@
             "\\begin{description}\n\\item[foo] ~ \n\\subsection{bar}\n\nbaz\n\\end{description}"
           , "containing image" =:
             header 1 (image "imgs/foo.jpg" "" (text "Alt text")) =?>
-            "\\section{\\texorpdfstring{\\protect\\pandocbounded{\\includegraphics[keepaspectratio,alt={Alt text}]{imgs/foo.jpg}}}{Alt text}}"
+            "\\section{\\texorpdfstring{\\protect\\pandocbounded{\\includegraphics[keepaspectratio,alt={Alt\ntext}]{imgs/foo.jpg}}}{Alt text}}"
           ]
         , testGroup "inline code"
           [ "struck out and highlighted" =:
@@ -249,7 +249,7 @@
             (plain $ image (pack "img.jpg") (pack "") (text "alt text"))
             =?>
               "\\begin{figure}[htbp]\n\\centering\n"
-              <> "\\pandocbounded{\\includegraphics[keepaspectratio,alt={alt text}]{img.jpg}}\n"
+              <> "\\pandocbounded{\\includegraphics[keepaspectratio,alt={alt\ntext}]{img.jpg}}\n"
               <> "\\caption{caption}\n\\end{figure}"
           ]
         ]
diff --git a/test/Tests/Writers/Markdown.hs b/test/Tests/Writers/Markdown.hs
--- a/test/Tests/Writers/Markdown.hs
+++ b/test/Tests/Writers/Markdown.hs
@@ -49,6 +49,26 @@
         , "emph/strong with spaces (#10696)"
              =: emph (str "f" <> strong (space <> str "d" <> space)) <> str "l" =?>
              "*f **d*** l"
+        , "image destination with space"
+             =: image "home/image folder/image.png" "title" "pic"
+             =?> "![pic](<home/image folder/image.png> \"title\")"
+        , "link destination with space and pointy brackets"
+             =: link "a b<c>.pdf" "" "link"
+             =?> "[link](<a b\\<c\\>.pdf>)"
+        , "bullet marker after linebreak in list item"
+             =: orderedList [ plain (str "Lead:" <> linebreak <>
+                                     str "-" <> space <> str "alpha") ]
+             =?> "1.  Lead:\\\n    \\- alpha\n"
+        , "ordered marker after linebreak in list item"
+             =: orderedList [ plain (str "Lead:" <> linebreak <>
+                                     str "3." <> space <> str "alpha") ]
+             =?> "1.  Lead:\\\n    3\\. alpha\n"
+        , "lone bullet marker after linebreak"
+             =: plain (str "Lead:" <> linebreak <> str "-")
+             =?> "Lead:\\\n\\-"
+        , "lone ordered marker after linebreak in list item"
+             =: orderedList [ plain (str "Lead:" <> linebreak <> str "3.") ]
+             =?> "1.  Lead:\\\n    3\\."
         ] ++ [noteTests] ++ [shortcutLinkRefsTests]
 
 {-
@@ -227,6 +247,12 @@
                        , "  [foo]: /url1"
                        , "  [1]: /url2"
                        , "  [2]: /url3"
+                       ]
+     , "Reference link destination with space"
+           =: para (link "my file.pdf" "" "link")
+           =?> unlines [ "[link]"
+                       , ""
+                       , "  [link]: <my file.pdf>"
                        ]
      , "Reference link is followed by text in brackets"
           =:  para (link "/url" "" "link" <> "[text in brackets]")
diff --git a/test/Tests/Writers/ODT.hs b/test/Tests/Writers/ODT.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/Writers/ODT.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE OverloadedStrings #-}
+{- |
+   Module      : Tests.Writers.ODT
+   Copyright   : © 2026 John MacFarlane
+   License     : GNU GPL, version 2 or above
+
+   Maintainer  : John MacFarlane <jgm@berkeley.edu>
+   Stability   : alpha
+   Portability : portable
+
+Tests for the ODT writers.
+-}
+module Tests.Writers.ODT (tests) where
+
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import Test.Tasty
+import Test.Tasty.HUnit
+import Text.Pandoc
+import Text.Pandoc.Builder
+import qualified Text.Pandoc.XML.Light as XML
+
+-- | The @xlink:href@ of every image in a flat OpenDocument file.  The
+-- local name is enough to match on, since the prefix is an artifact of
+-- the namespace declarations we happen to write.
+imageHrefs :: Text -> IO [Text]
+imageHrefs txt =
+  case XML.parseXMLElement (TL.fromStrict txt) of
+    Left msg    -> assertFailure $
+                     "flat OpenDocument output is not valid XML: "
+                     ++ T.unpack msg
+    Right root  -> return
+      [ href
+      | el        <- XML.filterElementsName ((== "image") . XML.qName) root
+      , Just href <- [XML.findAttrBy ((== "href") . XML.qName) el]
+      ]
+
+tests :: [TestTree]
+tests =
+  -- This cannot be a command test, because extracting the reference
+  -- from the output needs a pipe into grep, and the command tests run
+  -- under cmd.exe on Windows.
+  [ testCase "fodt --link-images leaves a relative reference alone" $ do
+      -- An ODT resolves relative references against its zip structure,
+      -- so the writer prefixes them with "../" (see fixInternalLinks).
+      -- A flat file is a single file, and must not have one.
+      fodt  <- runIOorExplode $ setVerbosity ERROR >>
+                 writeFODT def{ writerLinkImages = True } doc'
+      hrefs <- imageHrefs fodt
+      ["lalune.jpg"] @=? hrefs
+  ]
+ where
+  doc' = doc $ para $ image "lalune.jpg" "" mempty
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/11266.md b/test/command/11266.md
--- a/test/command/11266.md
+++ b/test/command/11266.md
@@ -9,7 +9,6 @@
 \end{eqnarray}
 $$
 ^D
-
 \begin{eqnarray}
 S &\rightarrow& a A                      \nonumber \\
 A &\rightarrow& d B \ | \ b A \ | \ c A  \nonumber \\
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/11845.md b/test/command/11845.md
new file mode 100644
--- /dev/null
+++ b/test/command/11845.md
@@ -0,0 +1,39 @@
+A heading's bookmark is written inside the heading's paragraph, around its
+text, rather than around its whole section, so a link to the heading
+survives a docx round trip, and a screen reader meets the bookmark at the
+heading (#11845).
+
+```
+% pandoc -f markdown -t docx -o - | pandoc -f docx -t native
+# Costs {#price}
+
+Back to [costs](#price), on to [later](#later-part).
+
+# Later Part
+
+End.
+^D
+[ Header 1 ( "costs" , [] , [] ) [ Str "Costs" ]
+, Para
+    [ Str "Back"
+    , Space
+    , Str "to"
+    , Space
+    , Link ( "" , [] , [] ) [ Str "costs" ] ( "#costs" , "" )
+    , Str ","
+    , Space
+    , Str "on"
+    , Space
+    , Str "to"
+    , Space
+    , Link
+        ( "" , [] , [] ) [ Str "later" ] ( "#later-part" , "" )
+    , Str "."
+    ]
+, Header
+    1
+    ( "later-part" , [] , [] )
+    [ Str "Later" , Space , Str "Part" ]
+, Para [ Str "End." ]
+]
+```
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/11927.md b/test/command/11927.md
new file mode 100644
--- /dev/null
+++ b/test/command/11927.md
@@ -0,0 +1,6 @@
+```
+% pandoc -f native -t commonmark
+[ Para [ Str "a\\(b\\)c" , Space , Str "\\*x" ] ]
+^D
+a\\(b\\)c \\\*x
+```
diff --git a/test/command/11939.md b/test/command/11939.md
new file mode 100644
--- /dev/null
+++ b/test/command/11939.md
@@ -0,0 +1,19 @@
+```
+% pandoc -f native -t markdown
+[ Table ("", [], [])
+    (Caption Nothing [Plain [Str "Caption"]])
+    [(AlignDefault, ColWidth 1)]
+    (TableHead ("", [], [])
+      [Row ("", [], [])
+        [Cell ("", [], []) AlignDefault (RowSpan 1) (ColSpan 1)
+          [Plain [Str "x"]]]])
+    [TableBody ("", [], []) (RowHeadColumns 0) [] []]
+    (TableFoot ("", [], []) [])
+]
+^D
++-----------------------------------------------------------------------+
+| x                                                                     |
++=======================================================================+
+
+: Caption
+```
diff --git a/test/command/11942.js b/test/command/11942.js
new file mode 100644
--- /dev/null
+++ b/test/command/11942.js
@@ -0,0 +1,1 @@
+var sel = '#main' & 1; // </script>
diff --git a/test/command/11942.md b/test/command/11942.md
new file mode 100644
--- /dev/null
+++ b/test/command/11942.md
@@ -0,0 +1,14 @@
+Characters with special meaning in a URI must be percent-encoded in a
+textual `data:` URI.  In particular a bare `#` would truncate the
+payload at the fragment delimiter, and bare `&` or `'` would be
+HTML-escaped into entities by the serializer.
+
+(The `</script>` in the resource is what forces the `data:` URI path
+rather than inlining the script contents.)
+
+```
+% pandoc --embed-resources
+<script src="command/11942.js"></script>
+^D
+<script src="data:text/javascript;charset=utf-8,var%20sel%20%3D%20%27%23main%27%20%26%201%3B%20%2F%2F%20%3C%2Fscript%3E%0A"></script>
+```
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/4880.md b/test/command/4880.md
--- a/test/command/4880.md
+++ b/test/command/4880.md
@@ -1,6 +1,6 @@
 ```
-% pandoc -t latex
-$x=y%comment$
+% pandoc -t latex -f native
+[Math InlineMath "x=y%comment"]
 ^D
 \(x=y%comment
 \)
diff --git a/test/command/5116.md b/test/command/5116.md
--- a/test/command/5116.md
+++ b/test/command/5116.md
@@ -12,7 +12,8 @@
 ^D
 \begin{figure}
 \centering
-\pandocbounded{\includegraphics[keepaspectratio,alt={This is a figure.}]{img.jpg}}
+\pandocbounded{\includegraphics[keepaspectratio,alt={This is a
+figure.}]{img.jpg}}
 \caption{This is a figure.}
 \end{figure}
 
@@ -50,7 +51,8 @@
 \begin{figure}
 \centering
 \caption{This is a figure.}
-\pandocbounded{\includegraphics[keepaspectratio,alt={This is a figure.}]{img.jpg}}
+\pandocbounded{\includegraphics[keepaspectratio,alt={This is a
+figure.}]{img.jpg}}
 \end{figure}
 
 \begin{longtable}[]{@{}rlcl@{}}
@@ -86,7 +88,8 @@
 ^D
 \begin{figure}
 \centering
-\pandocbounded{\includegraphics[keepaspectratio,alt={This is a figure.}]{img.jpg}}
+\pandocbounded{\includegraphics[keepaspectratio,alt={This is a
+figure.}]{img.jpg}}
 \caption{This is a figure.}
 \end{figure}
 
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/6792.md b/test/command/6792.md
--- a/test/command/6792.md
+++ b/test/command/6792.md
@@ -27,9 +27,6 @@
     <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Table_20_Contents">
       <style:paragraph-properties fo:text-align="center" style:justify-single-word="false" />
     </style:style>
-    <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Table_20_Contents">
-      <style:paragraph-properties fo:text-align="center" style:justify-single-word="false" />
-    </style:style>
     <style:style style:name="TableHeaderRowCell" style:family="table-cell">
       <style:table-cell-properties fo:border="none" />
     </style:style>
@@ -56,7 +53,7 @@
       <text:p text:style-name="P1">2</text:p>
     </table:table-cell>
     <table:table-cell table:style-name="TableRowCell" office:value-type="string">
-      <text:p text:style-name="P2">1</text:p>
+      <text:p text:style-name="P1">1</text:p>
     </table:table-cell>
   </table:table-row>
 </table:table>
diff --git a/test/command/6821.md b/test/command/6821.md
--- a/test/command/6821.md
+++ b/test/command/6821.md
@@ -60,7 +60,8 @@
 
 \begin{figure}
 \centering
-\pandocbounded{\includegraphics[keepaspectratio,alt={This is a test x = 5}]{test.png}}
+\pandocbounded{\includegraphics[keepaspectratio,alt={This is a test x =
+5}]{test.png}}
 \caption{This is a test
 \protect\VERB|\NormalTok{x }\OperatorTok{=} \DecValTok{5}|}
 \end{figure}
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/7181.md b/test/command/7181.md
--- a/test/command/7181.md
+++ b/test/command/7181.md
@@ -4,7 +4,8 @@
 ^D
 \begin{figure}
 \centering
-\pandocbounded{\includegraphics[keepaspectratio,page=13,trim=1cm,clip,width=4cm,alt={Global frog population.}]{slides.pdf}}
+\pandocbounded{\includegraphics[keepaspectratio,page=13,trim=1cm,clip,width=4cm,alt={Global
+frog population.}]{slides.pdf}}
 \caption{Global frog population.}
 \end{figure}
 
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/9002.md b/test/command/9002.md
--- a/test/command/9002.md
+++ b/test/command/9002.md
@@ -2,7 +2,8 @@
 % pandoc command/9002.docx -t html
 ^D
 <table style="width:40%;">
-<caption><p>This is my table!</p></caption>
+<caption><p><span id="_Ref143272803" class="anchor"></span> This is my
+table!</p></caption>
 <colgroup>
 <col style="width: 20%" />
 <col style="width: 20%" />
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" ] , [] )
+    ( "" , [ "tip" , "alert" ] , [] )
     [ 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
@@ -14,11 +14,11 @@
 
     # These should be filled in by pandoc:
     opts="-f -r --from --read -t -w --to --write -o --output --data-dir -M --metadata --metadata-file -d --defaults --file-scope --sandbox -s --standalone --template -V --variable --variable-json --wrap --ascii --toc --table-of-contents --toc-depth --lof --list-of-figures --lot --list-of-tables -N --number-sections --number-offset --top-level-division --extract-media --resource-path -H --include-in-header -B --include-before-body -A --include-after-body --no-highlight --highlight-style --syntax-definition --syntax-highlighting --dpi --eol --columns -p --preserve-tabs --tab-stop --pdf-engine --pdf-engine-opt --reference-doc --self-contained --embed-resources --link-images --request-header --no-check-certificate --abbreviations --typst-input --indented-code-classes --default-image-extension -F --filter -L --lua-filter --shift-heading-level-by --base-header-level --track-changes --strip-comments --reference-links --reference-location --figure-caption-position --table-caption-position --markdown-headings --list-tables --listings -i --incremental --slide-level --section-divs --html-q-tags --email-obfuscation --id-prefix -T --title-prefix -c --css --epub-subdirectory --epub-cover-image --epub-title-page --epub-metadata --epub-embed-font --split-level --chunk-template --epub-chapter-level --ipynb-output -C --citeproc --bibliography --csl --citation-abbreviations --natbib --biblatex --math-method --mathml --webtex --mathjax --katex --gladtex --trace --dump-args --ignore-args --verbose --quiet --fail-if-warnings --log --completion --bash-completion --list-input-formats --list-output-formats --list-extensions --list-highlight-languages --list-highlight-styles -D --print-default-template --print-default-data-file --print-highlight-style -v --version -h --help"
-    informats="asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml"
-    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"
+    informats="asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 fodt gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml"
+    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 fodt 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)
@@ -116,14 +116,14 @@
 _pandoc() {
   local -a args
   args=(
-    '-f[Reader format]:FORMAT:(asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml)'
-    '-r[Reader format]:FORMAT:(asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml)'
-    '--from[Reader format]:FORMAT:(asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml)'
-    '--read[Reader format]:FORMAT:(asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml)'
-    '-t[Writer format]: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)'
-    '-w[Writer format]: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)'
-    '--to[Writer format]: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)'
-    '--write[Writer format]: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)'
+    '-f[Reader format]:FORMAT:(asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 fodt gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml)'
+    '-r[Reader format]:FORMAT:(asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 fodt gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml)'
+    '--from[Reader format]:FORMAT:(asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 fodt gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml)'
+    '--read[Reader format]:FORMAT:(asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 fodt gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml)'
+    '-t[Writer format]: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 fodt 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)'
+    '-w[Writer format]: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 fodt 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)'
+    '--to[Writer format]: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 fodt 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)'
+    '--write[Writer format]: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 fodt 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)'
     '-o[Output file]:FILE:_files'
     '--output[Output file]:FILE:_files'
     '--data-dir[Directory for data files]:DIRECTORY:_files'
@@ -245,9 +245,9 @@
     '--list-extensions[List supported extensions]'
     '--list-highlight-languages[List highlighting languages]'
     '--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)'
+    '-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 fodt 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 fodt 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:(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]'
@@ -266,8 +266,8 @@
 ```
 % pandoc --completion=fish
 ^D
-complete -c pandoc -l from -d "Reader format" -r -a "asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml"
-complete -c pandoc -l to -d "Writer format" -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 from -d "Reader format" -r -a "asciidoc biblatex bibtex bits commonmark commonmark_x creole csljson csv djot docbook docx dokuwiki endnotexml epub fb2 fodt gfm haddock html ipynb jats jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict mdoc mediawiki muse native odt opml org pod pptx ris rst rtf t2t textile tikiwiki tsv twiki typst vimwiki xlsx xml"
+complete -c pandoc -l to -d "Writer format" -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 fodt 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 -s o -l output -d "Output file" -r
 complete -c pandoc -l data-dir -d "Directory for data files" -r
 complete -c pandoc -s M -l metadata -d "Metadata field KEY=VALUE" -r
@@ -370,8 +370,8 @@
 complete -c pandoc -l list-extensions -d "List supported extensions"
 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 -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 fodt 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 "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/fodt-roundtrip.md b/test/command/fodt-roundtrip.md
new file mode 100644
--- /dev/null
+++ b/test/command/fodt-roundtrip.md
@@ -0,0 +1,126 @@
+Round trips through the flat OpenDocument format.  The flat format has no
+zip structure, so a relative link is not rewritten, and an image is
+embedded as base64 in an `office:binary-data` element rather than being
+put in a `Pictures/` entry.
+
+```
+% pandoc -f markdown -t fodt | pandoc -f fodt -t native
+# A *heading*
+
+Inline $x^2$, display:
+
+$$y = 1$$
+
+| a | b |
+|---|---|
+| 1 | 2 |
+
+1. one
+2. two
+
+A footnote[^1] and a [relative link](foo/bar.html).
+
+[^1]: Note text.
+^D
+[ Header
+    1
+    ( "a-heading" , [] , [] )
+    [ Span ( "anchor" , [] , [] ) []
+    , Str "A"
+    , Space
+    , Emph [ Str "heading" ]
+    ]
+, Para
+    [ Str "Inline"
+    , Space
+    , Math DisplayMath "x^{2}"
+    , Str ","
+    , Space
+    , Str "display:"
+    ]
+, Para [ Math DisplayMath "y = 1" ]
+, Table
+    ( "" , [] , [] )
+    (Caption Nothing [])
+    [ ( AlignDefault , ColWidthDefault )
+    , ( AlignDefault , ColWidthDefault )
+    ]
+    (TableHead
+       ( "" , [] , [] )
+       [ Row
+           ( "" , [] , [] )
+           [ Cell
+               ( "" , [] , [] )
+               AlignDefault
+               (RowSpan 1)
+               (ColSpan 1)
+               [ Plain [ Strong [ Str "a" ] ] ]
+           , Cell
+               ( "" , [] , [] )
+               AlignDefault
+               (RowSpan 1)
+               (ColSpan 1)
+               [ Plain [ Strong [ Str "b" ] ] ]
+           ]
+       ])
+    [ TableBody
+        ( "" , [] , [] )
+        (RowHeadColumns 0)
+        []
+        [ Row
+            ( "" , [] , [] )
+            [ Cell
+                ( "" , [] , [] )
+                AlignDefault
+                (RowSpan 1)
+                (ColSpan 1)
+                [ Plain [ Str "1" ] ]
+            , Cell
+                ( "" , [] , [] )
+                AlignDefault
+                (RowSpan 1)
+                (ColSpan 1)
+                [ Plain [ Str "2" ] ]
+            ]
+        ]
+    ]
+    (TableFoot ( "" , [] , [] ) [])
+, OrderedList
+    ( 1 , Decimal , Period )
+    [ [ Plain [ Str "one" ] ] , [ Plain [ Str "two" ] ] ]
+, Para
+    [ Str "A"
+    , Space
+    , Str "footnote"
+    , Note [ Para [ Str "Note" , Space , Str "text." ] ]
+    , Space
+    , Str "and"
+    , Space
+    , Str "a"
+    , Space
+    , Link
+        ( "" , [] , [] )
+        [ Str "relative" , Space , Str "link" ]
+        ( "foo/bar.html" , "" )
+    , Str "."
+    ]
+]
+```
+
+The image data survives the base64 round trip.
+
+```
+% pandoc -f markdown -t fodt | pandoc -f fodt -t native
+![](lalune.jpg)
+^D
+[ Para
+    [ Image
+        ( ""
+        , []
+        , [ ( "width" , "150.0pt" ) , ( "height" , "150.0pt" ) ]
+        )
+        []
+        ( "Pictures/image1.jpg" , "" )
+    ]
+]
+```
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/opendocument-abstract.opendocument b/test/command/opendocument-abstract.opendocument
new file mode 100644
--- /dev/null
+++ b/test/command/opendocument-abstract.opendocument
@@ -0,0 +1,1 @@
+$abstract$
diff --git a/test/command/opendocument-body.opendocument b/test/command/opendocument-body.opendocument
new file mode 100644
--- /dev/null
+++ b/test/command/opendocument-body.opendocument
@@ -0,0 +1,1 @@
+$body$
diff --git a/test/command/opendocument-styles.md b/test/command/opendocument-styles.md
new file mode 100644
--- /dev/null
+++ b/test/command/opendocument-styles.md
@@ -0,0 +1,131 @@
+The `abstract` metadata field holds blocks, so it must be rendered
+with the block writer: previously it was flattened to inlines, which
+produced bare character data (not valid as a child of `office:text`)
+and dropped the `Abstract` style.
+
+```
+% pandoc -f markdown -t opendocument --template command/opendocument-abstract.opendocument
+---
+abstract: A one-line abstract.
+---
+
+Body.
+^D
+<text:p text:style-name="Abstract">A one-line abstract.</text:p>
+```
+
+A multi-paragraph abstract stays multi-paragraph:
+
+```
+% pandoc -f markdown -t opendocument --wrap=none --template command/opendocument-abstract.opendocument
+---
+abstract: |
+  First para.
+
+  Second para.
+---
+
+Body.
+^D
+<text:p text:style-name="Abstract">First para.</text:p><text:p text:style-name="Abstract">Second para.</text:p>
+```
+
+A requested paragraph style must also be applied to `Plain` blocks, not
+just to `Para`. The body of a caption-less figure is a `Plain`, so it
+should still get the `Figure` style:
+
+```
+% pandoc -f native -t opendocument --template command/opendocument-body.opendocument
+[Figure ("fig1",[],[]) (Caption Nothing []) [Plain [Str "placeholder"]]]
+^D
+<text:p text:style-name="Figure">placeholder</text:p>
+```
+
+Column style names must remain valid NCNames past the 26th column;
+incrementing a character code would run into `[`, `\`, etc.
+
+```
+% pandoc -f native -t opendocument --template command/opendocument-body.opendocument
+[Table ("",[],[]) (Caption Nothing [])
+ [(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)
+ ,(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)
+ ,(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)
+ ,(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)
+ ,(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)
+ ,(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)
+ ,(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)]
+ (TableHead ("",[],[]) [])
+ [(TableBody ("",[],[]) (RowHeadColumns 0) [] [])]
+ (TableFoot ("",[],[]) [])]
+^D
+<table:table table:name="Table1" table:style-name="Table1">
+  <table:table-column table:style-name="Table1.A" />
+  <table:table-column table:style-name="Table1.B" />
+  <table:table-column table:style-name="Table1.C" />
+  <table:table-column table:style-name="Table1.D" />
+  <table:table-column table:style-name="Table1.E" />
+  <table:table-column table:style-name="Table1.F" />
+  <table:table-column table:style-name="Table1.G" />
+  <table:table-column table:style-name="Table1.H" />
+  <table:table-column table:style-name="Table1.I" />
+  <table:table-column table:style-name="Table1.J" />
+  <table:table-column table:style-name="Table1.K" />
+  <table:table-column table:style-name="Table1.L" />
+  <table:table-column table:style-name="Table1.M" />
+  <table:table-column table:style-name="Table1.N" />
+  <table:table-column table:style-name="Table1.O" />
+  <table:table-column table:style-name="Table1.P" />
+  <table:table-column table:style-name="Table1.Q" />
+  <table:table-column table:style-name="Table1.R" />
+  <table:table-column table:style-name="Table1.S" />
+  <table:table-column table:style-name="Table1.T" />
+  <table:table-column table:style-name="Table1.U" />
+  <table:table-column table:style-name="Table1.V" />
+  <table:table-column table:style-name="Table1.W" />
+  <table:table-column table:style-name="Table1.X" />
+  <table:table-column table:style-name="Table1.Y" />
+  <table:table-column table:style-name="Table1.Z" />
+  <table:table-column table:style-name="Table1.AA" />
+</table:table>
+```
+
+A note nested inside another note must not reuse the outer note's
+`text:id`:
+
+```
+% pandoc -f markdown -t opendocument --wrap=none --template command/opendocument-body.opendocument
+Text^[outer^[inner]]^[second]
+^D
+<text:p text:style-name="Text_20_body">Text<text:note text:id="ftn0" text:note-class="footnote"><text:note-citation>1</text:note-citation><text:note-body><text:p text:style-name="Footnote">outer<text:note text:id="ftn1" text:note-class="footnote"><text:note-citation>2</text:note-citation><text:note-body><text:p text:style-name="Footnote">inner</text:p></text:note-body></text:note></text:p></text:note-body></text:note><text:note text:id="ftn2" text:note-class="footnote"><text:note-citation>3</text:note-citation><text:note-body><text:p text:style-name="Footnote">second</text:p></text:note-body></text:note></text:p>
+```
+
+Automatic text styles are declared in numerical order: sorting them by
+name would put `T10` between `T1` and `T2`.
+
+```
+% pandoc -f native -t opendocument --wrap=none --template command/11301-styles.opendocument
+[Para [SmallCaps [Str "a"]
+      ,SmallCaps [Strikeout [Str "b"]]
+      ,SmallCaps [Superscript [Str "c"]]
+      ,SmallCaps [Subscript [Str "d"]]
+      ,SmallCaps [Underline [Str "e"]]
+      ,Strikeout [Superscript [Str "f"]]
+      ,Strikeout [Subscript [Str "g"]]
+      ,Strikeout [Underline [Str "h"]]
+      ,Underline [Superscript [Str "i"]]
+      ,Underline [Subscript [Str "j"]]]]
+^D
+<style:style style:name="T1" style:family="text"><style:text-properties fo:font-variant="small-caps" /></style:style>
+<style:style style:name="T2" style:family="text"><style:text-properties fo:font-variant="small-caps" style:text-line-through-style="solid" /></style:style>
+<style:style style:name="T3" style:family="text"><style:text-properties fo:font-variant="small-caps" style:text-position="super 58%" /></style:style>
+<style:style style:name="T4" style:family="text"><style:text-properties fo:font-variant="small-caps" style:text-position="sub 58%" /></style:style>
+<style:style style:name="T5" style:family="text"><style:text-properties fo:font-variant="small-caps" style:text-underline-color="font-color" style:text-underline-style="solid" style:text-underline-width="auto" /></style:style>
+<style:style style:name="T6" style:family="text"><style:text-properties style:text-line-through-style="solid" style:text-position="super 58%" /></style:style>
+<style:style style:name="T7" style:family="text"><style:text-properties style:text-line-through-style="solid" style:text-position="sub 58%" /></style:style>
+<style:style style:name="T8" style:family="text"><style:text-properties style:text-line-through-style="solid" style:text-underline-color="font-color" style:text-underline-style="solid" style:text-underline-width="auto" /></style:style>
+<style:style style:name="T9" style:family="text"><style:text-properties style:text-position="super 58%" style:text-underline-color="font-color" style:text-underline-style="solid" style:text-underline-width="auto" /></style:style>
+<style:style style:name="T10" style:family="text"><style:text-properties style:text-position="sub 58%" style:text-underline-color="font-color" style:text-underline-style="solid" style:text-underline-width="auto" /></style:style>
+<style:style style:name="fr2" style:family="graphic" style:parent-style-name="Formula"><style:graphic-properties style:vertical-pos="middle" style:vertical-rel="text" style:horizontal-pos="center" style:horizontal-rel="paragraph-content" style:wrap="none" /></style:style>
+<style:style style:name="fr1" style:family="graphic" style:parent-style-name="Formula"><style:graphic-properties style:vertical-pos="middle" style:vertical-rel="text" /></style:style>
+<text:p text:style-name="Text_20_body"><text:span text:style-name="T1">a</text:span><text:span text:style-name="T2">b</text:span><text:span text:style-name="T3">c</text:span><text:span text:style-name="T4">d</text:span><text:span text:style-name="T5">e</text:span><text:span text:style-name="T6">f</text:span><text:span text:style-name="T7">g</text:span><text:span text:style-name="T8">h</text:span><text:span text:style-name="T9">i</text:span><text:span text:style-name="T10">j</text:span></text:p>
+```
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-77.md b/test/command/pandoc-citeproc-77.md
--- a/test/command/pandoc-citeproc-77.md
+++ b/test/command/pandoc-citeproc-77.md
@@ -1,3 +1,6 @@
+Note: pandoc no longer parses particles out of names (see #11911).
+This test is left to confirm the new behavior.
+
 ```
 % pandoc --citeproc -t markdown-citations
 ---
@@ -41,7 +44,7 @@
 
 :::::::: {#refs .references .csl-bib-body .hanging-indent entry-spacing="0"}
 ::: {#ref-item4 .csl-entry}
-Bennett, Frank G., Jr., n.d.
+Bennett, Frank G.,! Jr., n.d.
 :::
 
 ::: {#ref-item1 .csl-entry}
@@ -52,16 +55,16 @@
 Dumboldt, Ezekiel, III, n.d.
 :::
 
-::: {#ref-item2 .csl-entry}
-Gogh, Vincent van, n.d.
-:::
-
 ::: {#ref-item3 .csl-entry}
 Humboldt, Alexander von, n.d.
 :::
+
+::: {#ref-item2 .csl-entry}
+[van Gogh, Vincent]{.nocase}, n.d.
+:::
 ::::::::
 
-[^1]: John Doe III, n.d.; Vincent van Gogh, n.d.; Alexander von
-    Humboldt, n.d.; Frank G. Bennett, Jr., n.d.; Ezekiel Dumboldt III,
-    n.d.
+[^1]: John, III Doe, n.d.; [Vincent van Gogh]{.nocase}, n.d.; Alexander
+    von Humboldt, n.d.; Frank G.,! Jr. Bennett, n.d.; Ezekiel, III
+    Dumboldt, n.d.
 ```
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/double_strikeout.docx b/test/docx/double_strikeout.docx
new file mode 100644
Binary files /dev/null and b/test/docx/double_strikeout.docx differ
diff --git a/test/docx/double_strikeout.native b/test/docx/double_strikeout.native
new file mode 100644
--- /dev/null
+++ b/test/docx/double_strikeout.native
@@ -0,0 +1,68 @@
+Pandoc
+  Meta { unMeta = fromList [] }
+  [ Para
+      [ Str "This"
+      , Space
+      , Str "is"
+      , Space
+      , Strikeout [ Str "single" , Space , Str "strikethrough" ]
+      , Str ","
+      , Space
+      , Str "and"
+      , Space
+      , Str "this"
+      , Space
+      , Str "is"
+      , Space
+      , Strikeout [ Str "double" , Space , Str "strikethrough" ]
+      , Str "."
+      ]
+  , Para
+      [ Str "A"
+      , Space
+      , Str "character"
+      , Space
+      , Str "style"
+      , Space
+      , Str "can"
+      , Space
+      , Strikeout
+          [ Str "strike" , Space , Str "this" , Space , Str "out," ]
+      , Space
+      , Str "cancel"
+      , Space
+      , Str "this,"
+      , Space
+      , Strikeout
+          [ Str "or"
+          , Space
+          , Str "strike"
+          , Space
+          , Str "this"
+          , Space
+          , Str "out"
+          , Space
+          , Str "twice"
+          ]
+      , Str "."
+      ]
+  , Para
+      [ Str "This"
+      , Space
+      , Str "is"
+      , Space
+      , Strikeout
+          [ Str "double-strikethrough,"
+          , Space
+          , Str "overridden"
+          , Space
+          , Str "to"
+          , Space
+          , Str "single,"
+          , Space
+          , Str "and"
+          ]
+      , Space
+      , Str "cancelled."
+      ]
+  ]
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/codeblock.docx b/test/docx/golden/codeblock.docx
Binary files a/test/docx/golden/codeblock.docx and b/test/docx/golden/codeblock.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_no_reference.docx b/test/docx/golden/custom_style_no_reference.docx
Binary files a/test/docx/golden/custom_style_no_reference.docx and b/test/docx/golden/custom_style_no_reference.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/definition_list.docx b/test/docx/golden/definition_list.docx
Binary files a/test/docx/golden/definition_list.docx and b/test/docx/golden/definition_list.docx differ
diff --git a/test/docx/golden/document-properties-short-desc.docx b/test/docx/golden/document-properties-short-desc.docx
Binary files a/test/docx/golden/document-properties-short-desc.docx and b/test/docx/golden/document-properties-short-desc.docx differ
diff --git a/test/docx/golden/document-properties.docx b/test/docx/golden/document-properties.docx
Binary files a/test/docx/golden/document-properties.docx and b/test/docx/golden/document-properties.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/inline_code.docx b/test/docx/golden/inline_code.docx
Binary files a/test/docx/golden/inline_code.docx and b/test/docx/golden/inline_code.docx differ
diff --git a/test/docx/golden/inline_formatting.docx b/test/docx/golden/inline_formatting.docx
Binary files a/test/docx/golden/inline_formatting.docx and b/test/docx/golden/inline_formatting.docx differ
diff --git a/test/docx/golden/inline_images.docx b/test/docx/golden/inline_images.docx
Binary files a/test/docx/golden/inline_images.docx and b/test/docx/golden/inline_images.docx differ
diff --git a/test/docx/golden/link_in_notes.docx b/test/docx/golden/link_in_notes.docx
Binary files a/test/docx/golden/link_in_notes.docx and b/test/docx/golden/link_in_notes.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_9994.docx b/test/docx/golden/lists_9994.docx
Binary files a/test/docx/golden/lists_9994.docx and b/test/docx/golden/lists_9994.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/mark_math.docx b/test/docx/golden/mark_math.docx
new file mode 100644
Binary files /dev/null and b/test/docx/golden/mark_math.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/raw-blocks.docx b/test/docx/golden/raw-blocks.docx
Binary files a/test/docx/golden/raw-blocks.docx and b/test/docx/golden/raw-blocks.docx differ
diff --git a/test/docx/golden/raw-bookmarks.docx b/test/docx/golden/raw-bookmarks.docx
Binary files a/test/docx/golden/raw-bookmarks.docx and b/test/docx/golden/raw-bookmarks.docx differ
diff --git a/test/docx/golden/table_one_row.docx b/test/docx/golden/table_one_row.docx
Binary files a/test/docx/golden/table_one_row.docx and b/test/docx/golden/table_one_row.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/tables_separated_with_rawblock.docx b/test/docx/golden/tables_separated_with_rawblock.docx
Binary files a/test/docx/golden/tables_separated_with_rawblock.docx and b/test/docx/golden/tables_separated_with_rawblock.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_deletion.docx b/test/docx/golden/track_changes_deletion.docx
Binary files a/test/docx/golden/track_changes_deletion.docx and b/test/docx/golden/track_changes_deletion.docx differ
diff --git a/test/docx/golden/track_changes_insertion.docx b/test/docx/golden/track_changes_insertion.docx
Binary files a/test/docx/golden/track_changes_insertion.docx and b/test/docx/golden/track_changes_insertion.docx differ
diff --git a/test/docx/golden/track_changes_move.docx b/test/docx/golden/track_changes_move.docx
Binary files a/test/docx/golden/track_changes_move.docx and b/test/docx/golden/track_changes_move.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/golden/unicode.docx b/test/docx/golden/unicode.docx
Binary files a/test/docx/golden/unicode.docx and b/test/docx/golden/unicode.docx differ
diff --git a/test/docx/golden/verbatim_subsuper.docx b/test/docx/golden/verbatim_subsuper.docx
Binary files a/test/docx/golden/verbatim_subsuper.docx and b/test/docx/golden/verbatim_subsuper.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/mark_math.native b/test/docx/mark_math.native
new file mode 100644
--- /dev/null
+++ b/test/docx/mark_math.native
@@ -0,0 +1,12 @@
+[ Para
+    [ Span
+        ( "" , [ "mark" ] , [] )
+        [ Str "a " , Math InlineMath "x^2" , Str " b" ]
+    ]
+, Para
+    [ Span ( "" , [ "mark" ] , [] ) [ Math DisplayMath "z = 1" ] ]
+, Para [ Span ( "" , [ "mark" ] , [] ) [ Math InlineMath "\\hat{x}" ] ]
+, Para [ Span ( "" , [ "mark" ] , [] )
+  [ Math InlineMath "\\sin x + \\sum_{i=1}^n i + \\frac{a}{b} + \\sqrt[3]{x}" ] ]
+, Para [ Str "unmarked " , Math InlineMath "x^2" , Str " math" ]
+]
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_edge_spaces.docx b/test/docx/track_changes_edge_spaces.docx
new file mode 100644
Binary files /dev/null and b/test/docx/track_changes_edge_spaces.docx differ
diff --git a/test/docx/track_changes_edge_spaces_accept.native b/test/docx/track_changes_edge_spaces_accept.native
new file mode 100644
--- /dev/null
+++ b/test/docx/track_changes_edge_spaces_accept.native
@@ -0,0 +1,4 @@
+[Para [Str "start-ups",Space,Str "grow."]
+,Para [Str "adog"]
+,Para [Str "the",Space,Str "new."]
+,Para [Str "an",Space,Emph [Str "italic"],Space,Str "word."]]
diff --git a/test/docx/track_changes_edge_spaces_all.native b/test/docx/track_changes_edge_spaces_all.native
new file mode 100644
--- /dev/null
+++ b/test/docx/track_changes_edge_spaces_all.native
@@ -0,0 +1,4 @@
+[Para [Str "start-up",Span ("",["deletion"],[("author","eng-dept"),("date","2026-09-25T10:00:00Z")]) [Space,Str "entry"],Span ("",["insertion"],[("author","eng-dept"),("date","2026-09-25T10:00:00Z")]) [Str "s"],Space,Str "grow."]
+,Para [Str "a",Span ("",["deletion"],[("author","eng-dept"),("date","2026-09-25T10:00:00Z")]) [Str "big",Space],Str "dog"]
+,Para [Str "the",Span ("",["insertion"],[("author","eng-dept"),("date","2026-09-25T10:00:00Z")]) [Space,Str "new"],Str "."]
+,Para [Str "an",Span ("",["insertion"],[("author","eng-dept"),("date","2026-09-25T10:00:00Z")]) [Space,Emph [Str "italic"]],Space,Str "word."]]
diff --git a/test/docx/track_changes_edge_spaces_reject.native b/test/docx/track_changes_edge_spaces_reject.native
new file mode 100644
--- /dev/null
+++ b/test/docx/track_changes_edge_spaces_reject.native
@@ -0,0 +1,4 @@
+[Para [Str "start-up",Space,Str "entry",Space,Str "grow."]
+,Para [Str "abig",Space,Str "dog"]
+,Para [Str "the."]
+,Para [Str "an",Space,Str "word."]]
diff --git a/test/docx/track_changes_insertion_all.native b/test/docx/track_changes_insertion_all.native
--- a/test/docx/track_changes_insertion_all.native
+++ b/test/docx/track_changes_insertion_all.native
@@ -1,1 +1,1 @@
-[Para [Str "This",Space,Str "is",Space,Str "a",Space,Str "text",Space,Str "with",Space,Span ("",["insertion"],[("author","eng-dept"),("date","2014-06-25T10:40:00Z")]) [Str "two",Space,Str "exciting"],Space,Str "insertions."]]
+[Para [Str "This",Space,Str "is",Space,Str "a",Space,Str "text",Space,Str "with",Space,Span ("",["insertion"],[("author","eng-dept"),("date","2014-06-25T10:40:00Z")]) [Str "two",Space,Str "exciting",Space],Str "insertions."]]
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/fodt-native/image.native b/test/odt/fodt-native/image.native
new file mode 100644
--- /dev/null
+++ b/test/odt/fodt-native/image.native
@@ -0,0 +1,1 @@
+[Para [Image ("",[],[("width","2.0835in"),("height","2.0835in")]) [] ("Pictures/image1.jpg","")]]
diff --git a/test/odt/fodt/formula.fodt b/test/odt/fodt/formula.fodt
new file mode 100644
--- /dev/null
+++ b/test/odt/fodt/formula.fodt
@@ -0,0 +1,523 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>Martin Linnemann</meta:initial-creator><meta:creation-date>2015-03-01T17:26:12.31</meta:creation-date><dc:date>2015-03-01T18:07:27.67</dc:date><dc:creator>Martin Linnemann</dc:creator><meta:editing-duration>PT10M3S</meta:editing-duration><meta:editing-cycles>2</meta:editing-cycles><meta:generator>LibreOffice/24.8.7.2$Linux_AARCH64 LibreOffice_project/480$Build-2</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="1" meta:page-count="1" meta:paragraph-count="1" meta:word-count="0" meta:character-count="1"/></office:meta>
+ <office:font-face-decls>
+  <style:font-face style:name="Arial" svg:font-family="Arial" style:font-family-generic="swiss" style:font-pitch="variable"/>
+  <style:font-face style:name="Calibri" svg:font-family="Calibri" style:font-family-generic="swiss"/>
+  <style:font-face style:name="Lucida Sans Unicode" svg:font-family="&apos;Lucida Sans Unicode&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="Mangal" svg:font-family="Mangal"/>
+  <style:font-face style:name="Mangal1" svg:font-family="Mangal" style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="OpenSymbol" svg:font-family="OpenSymbol"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="graphic">
+   <style:graphic-properties svg:stroke-color="#808080" draw:fill-color="#cfe7f5" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+   <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0in" style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Calibri" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Lucida Sans Unicode" style:font-size-asian="12pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Mangal1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+  </style:default-style>
+  <style:default-style style:family="paragraph">
+   <style:paragraph-properties fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Calibri" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Lucida Sans Unicode" style:font-size-asian="12pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Mangal1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:default-style>
+  <style:default-style style:family="table">
+   <style:table-properties table:border-model="collapsing"/>
+  </style:default-style>
+  <style:default-style style:family="table-row">
+   <style:table-row-properties fo:keep-together="auto"/>
+  </style:default-style>
+  <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+  <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+   <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" fo:keep-with-next="always"/>
+   <style:text-properties style:font-name="Arial" fo:font-family="Arial" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="Lucida Sans Unicode" style:font-family-asian="&apos;Lucida Sans Unicode&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Mangal1" style:font-family-complex="Mangal" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+  </style:style>
+  <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+   <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0835in" style:contextual-spacing="false"/>
+  </style:style>
+  <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
+   <style:text-properties style:font-name="Calibri" fo:font-family="Calibri" style:font-family-generic="swiss" style:font-name-complex="Mangal" style:font-family-complex="Mangal"/>
+  </style:style>
+  <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties style:font-name="Calibri" fo:font-family="Calibri" style:font-family-generic="swiss" fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="Mangal" style:font-family-complex="Mangal" style:font-size-complex="12pt" style:font-style-complex="italic"/>
+  </style:style>
+  <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
+   <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+   <style:text-properties style:font-name="Calibri" fo:font-family="Calibri" style:font-family-generic="swiss" style:font-name-complex="Mangal" style:font-family-complex="Mangal"/>
+  </style:style>
+  <style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text">
+   <style:text-properties fo:font-size="115%" fo:font-weight="bold" style:font-size-asian="115%" style:font-weight-asian="bold" style:font-size-complex="115%" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text">
+   <style:text-properties fo:font-size="14pt" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="14pt" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-style-complex="italic" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+  </style:style>
+  <style:style style:name="Table_20_Heading" style:display-name="Table Heading" style:family="paragraph" style:parent-style-name="Table_20_Contents" style:class="extra">
+   <style:paragraph-properties fo:text-align="center" style:justify-single-word="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Footnote" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties fo:margin-left="0.1965in" fo:margin-right="0in" fo:text-indent="-0.1965in" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
+  </style:style>
+  <style:style style:name="Index_20_Heading" style:display-name="Index Heading" style:family="paragraph" style:parent-style-name="Heading" style:class="index">
+   <style:paragraph-properties fo:margin-left="0in" fo:text-indent="0in" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="16pt" style:font-weight-asian="bold" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Bibliography_20_Heading" style:display-name="Bibliography Heading" style:family="paragraph" style:parent-style-name="Heading" style:class="index">
+   <style:paragraph-properties fo:margin-left="0in" fo:margin-right="0in" fo:text-indent="0in" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="16pt" style:font-weight-asian="bold" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Bibliography_20_1" style:display-name="Bibliography 1" style:family="paragraph" style:parent-style-name="Index" style:class="index">
+   <style:paragraph-properties fo:margin-left="0in" fo:margin-right="0in" fo:text-indent="0in" style:auto-text-indent="false">
+    <style:tab-stops>
+     <style:tab-stop style:position="6.6929in" style:type="right" style:leader-style="dotted" style:leader-text="."/>
+    </style:tab-stops>
+   </style:paragraph-properties>
+  </style:style>
+  <style:style style:name="Endnote" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties fo:margin-left="0.1965in" fo:margin-right="0in" fo:text-indent="-0.1965in" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
+  </style:style>
+  <style:style style:name="Bullet_20_Symbols" style:display-name="Bullet Symbols" style:family="text">
+   <style:text-properties style:font-name="OpenSymbol" fo:font-family="OpenSymbol" style:font-name-asian="OpenSymbol" style:font-family-asian="OpenSymbol" style:font-name-complex="OpenSymbol" style:font-family-complex="OpenSymbol"/>
+  </style:style>
+  <style:style style:name="Numbering_20_Symbols" style:display-name="Numbering Symbols" style:family="text"/>
+  <style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">
+   <style:text-properties fo:color="#000080" loext:opacity="100%" fo:language="zxx" fo:country="none" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/>
+  </style:style>
+  <style:style style:name="Footnote_20_Symbol" style:display-name="Footnote Symbol" style:family="text"/>
+  <style:style style:name="Footnote_20_anchor" style:display-name="Footnote anchor" style:family="text">
+   <style:text-properties style:text-position="super 58%"/>
+  </style:style>
+  <style:style style:name="Endnote_20_Symbol" style:display-name="Endnote Symbol" style:family="text"/>
+  <style:style style:name="Endnote_20_anchor" style:display-name="Endnote anchor" style:family="text">
+   <style:text-properties style:text-position="super 58%"/>
+  </style:style>
+  <style:style style:name="OLE" style:family="graphic">
+   <style:graphic-properties text:anchor-type="paragraph" svg:x="0in" svg:y="0in" style:wrap="none" style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph" draw:fill="none"/>
+  </style:style>
+  <style:style style:name="Formula" style:family="graphic">
+   <style:graphic-properties text:anchor-type="as-char" svg:y="0in" fo:margin-left="0.0791in" fo:margin-right="0.0791in" style:vertical-pos="middle" style:vertical-rel="text" draw:fill="none"/>
+  </style:style>
+  <text:outline-style style:name="Outline">
+   <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.3in" fo:text-indent="-0.3in" fo:margin-left="0.3in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.4in" fo:text-indent="-0.4in" fo:margin-left="0.4in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.5in" fo:text-indent="-0.5in" fo:margin-left="0.5in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.6in" fo:text-indent="-0.6in" fo:margin-left="0.6in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7in" fo:text-indent="-0.7in" fo:margin-left="0.7in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.8in" fo:text-indent="-0.8in" fo:margin-left="0.8in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.9in" fo:text-indent="-0.9in" fo:margin-left="0.9in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1in" fo:text-indent="-1in" fo:margin-left="1in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.1in" fo:text-indent="-1.1in" fo:margin-left="1.1in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.2in" fo:text-indent="-1.2in" fo:margin-left="1.2in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+  </text:outline-style>
+  <text:list-style style:name="Numbering_20_1" style:display-name="Numbering 1">
+   <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.1965in" fo:text-indent="-0.1965in" fo:margin-left="0.1965in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%2%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.3937in" fo:text-indent="-0.1965in" fo:margin-left="0.3937in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.5902in" fo:text-indent="-0.1965in" fo:margin-left="0.5902in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7874in" fo:text-indent="-0.1965in" fo:margin-left="0.7874in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.9839in" fo:text-indent="-0.1965in" fo:margin-left="0.9839in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.1811in" fo:text-indent="-0.1965in" fo:margin-left="1.1811in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.378in" fo:text-indent="-0.1965in" fo:margin-left="1.378in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.5752in" fo:text-indent="-0.1965in" fo:margin-left="1.5752in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.7717in" fo:text-indent="-0.1965in" fo:margin-left="1.7717in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.9689in" fo:text-indent="-0.1965in" fo:margin-left="1.9689in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+  </text:list-style>
+  <text:list-style style:name="Numbering_20_2" style:display-name="Numbering 2">
+   <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%" style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.1965in" fo:text-indent="-0.1965in" fo:margin-left="0.1965in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%2%" style:num-format="1" text:start-value="2">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.3929in" fo:text-indent="-0.1965in" fo:margin-left="0.3929in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%" style:num-format="1" text:start-value="3">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7866in" fo:text-indent="-0.3937in" fo:margin-left="0.7866in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%" style:num-format="1" text:start-value="4">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.2791in" fo:text-indent="-0.4925in" fo:margin-left="1.2791in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%" style:num-format="1" text:start-value="5">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.8693in" fo:text-indent="-0.5902in" fo:margin-left="1.8693in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%" style:num-format="1" text:start-value="6">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.5783in" fo:text-indent="-0.7091in" fo:margin-left="2.5783in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%" style:num-format="1" text:start-value="7">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.4839in" fo:text-indent="-0.9055in" fo:margin-left="3.4839in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%" style:num-format="1" text:start-value="8">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="4.5075in" fo:text-indent="-1.0236in" fo:margin-left="4.5075in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%" style:num-format="1" text:start-value="9">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.6102in" fo:text-indent="-1.1028in" fo:margin-left="5.6102in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%" style:num-format="1" text:start-value="10">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="6.8311in" fo:text-indent="-1.2209in" fo:margin-left="6.8311in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+  </text:list-style>
+  <text:list-style style:name="Numbering_20_3" style:display-name="Numbering 3">
+   <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%" style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.1811in" fo:text-indent="-1.1811in" fo:margin-left="1.1811in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%2%" style:num-format="1" text:start-value="2">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.3626in" fo:text-indent="-1.1811in" fo:margin-left="2.3626in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%" style:num-format="1" text:start-value="3">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.5437in" fo:text-indent="-1.1811in" fo:margin-left="3.5437in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%" style:num-format="1" text:start-value="4">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="4.7252in" fo:text-indent="-1.1811in" fo:margin-left="4.7252in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%" style:num-format="1" text:start-value="5">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.9063in" fo:text-indent="-1.1811in" fo:margin-left="5.9063in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%" style:num-format="1" text:start-value="6">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="7.0874in" fo:text-indent="-1.1811in" fo:margin-left="7.0874in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%" style:num-format="1" text:start-value="7">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="8.2689in" fo:text-indent="-1.1811in" fo:margin-left="8.2689in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%" style:num-format="1" text:start-value="8">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="9.45in" fo:text-indent="-1.1811in" fo:margin-left="9.45in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%" style:num-format="1" text:start-value="9">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="10.6311in" fo:text-indent="-1.1811in" fo:margin-left="10.6311in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%" style:num-format="1" text:start-value="10">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="11.8126in" fo:text-indent="-1.1811in" fo:margin-left="11.8126in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+  </text:list-style>
+  <text:list-style style:name="Numbering_20_4" style:display-name="Numbering 4">
+   <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="0.1965in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%2%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="0.3937in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="0.5902in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="0.7874in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="0.9839in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="1.1811in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="1.378in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="1.5752in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="1.7717in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="1.9689in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+  </text:list-style>
+  <text:list-style style:name="Numbering_20_5" style:display-name="Numbering 5">
+   <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.1575in" fo:text-indent="-0.1575in" fo:margin-left="0.1575in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%.%2%." style:num-suffix="." style:num-format="1" text:display-levels="2">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.4335in" fo:text-indent="-0.2563in" fo:margin-left="0.4335in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%)" style:num-suffix=")" style:num-format="a">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.6102in" fo:text-indent="-0.1772in" fo:margin-left="0.6102in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-bullet text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7874in" fo:text-indent="-0.1555in" fo:margin-left="0.7874in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.9429in" fo:text-indent="-0.1555in" fo:margin-left="0.9429in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.0984in" fo:text-indent="-0.1555in" fo:margin-left="1.0984in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.2543in" fo:text-indent="-0.1555in" fo:margin-left="1.2543in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.4098in" fo:text-indent="-0.1555in" fo:margin-left="1.4098in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.5654in" fo:text-indent="-0.1555in" fo:margin-left="1.5654in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.7209in" fo:text-indent="-0.1555in" fo:margin-left="1.7209in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+  </text:list-style>
+  <text:notes-configuration text:note-class="footnote" text:citation-style-name="Footnote_20_Symbol" text:citation-body-style-name="Footnote_20_anchor" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+  <text:notes-configuration text:note-class="endnote" text:citation-style-name="Endnote_20_Symbol" text:citation-body-style-name="Endnote_20_anchor" text:master-page-name="Endnote" style:num-format="i" text:start-value="0"/>
+  <text:bibliography-configuration text:prefix="[" text:suffix="]" text:sort-algorithm="alphanumeric" fo:language="en" fo:country="US"/>
+  <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
+  <loext:theme loext:name="Office">
+   <loext:theme-colors loext:name="LibreOffice">
+    <loext:color loext:name="dark1" loext:color="#000000"/>
+    <loext:color loext:name="light1" loext:color="#ffffff"/>
+    <loext:color loext:name="dark2" loext:color="#000000"/>
+    <loext:color loext:name="light2" loext:color="#ffffff"/>
+    <loext:color loext:name="accent1" loext:color="#18a303"/>
+    <loext:color loext:name="accent2" loext:color="#0369a3"/>
+    <loext:color loext:name="accent3" loext:color="#a33e03"/>
+    <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+    <loext:color loext:name="accent5" loext:color="#c99c00"/>
+    <loext:color loext:name="accent6" loext:color="#c9211e"/>
+    <loext:color loext:name="hyperlink" loext:color="#0000ee"/>
+    <loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
+   </loext:theme-colors>
+  </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="fr1" style:family="graphic" style:parent-style-name="Formula">
+   <style:graphic-properties style:vertical-pos="middle" style:vertical-rel="text" style:horizontal-pos="from-left" style:horizontal-rel="paragraph-content" draw:ole-draw-aspect="1"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in">
+    <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+   </style:page-layout-properties>
+   <style:header-style/>
+   <style:footer-style/>
+  </style:page-layout>
+  <style:page-layout style:name="pm2">
+   <style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in">
+    <style:footnote-sep style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+   </style:page-layout-properties>
+   <style:header-style/>
+   <style:footer-style/>
+  </style:page-layout>
+  <style:style style:name="dp1" style:family="drawing-page">
+   <style:drawing-page-properties draw:background-size="full"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:body>
+  <office:text>
+   <text:sequence-decls>
+    <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+   </text:sequence-decls>
+   <text:p text:style-name="Standard"><draw:frame draw:style-name="fr1" draw:name="Objekt1" text:anchor-type="as-char" svg:width="0.6602in" svg:height="0.2075in" draw:z-index="0"><draw:object>
+      <math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+       <semantics>
+        <mrow>
+         <mi>E</mi>
+         <mo stretchy="false">=</mo>
+         <mrow>
+          <mi>m</mi>
+          <mo stretchy="false">⋅</mo>
+          <msup>
+           <mi>c</mi>
+           <mn>2</mn>
+          </msup>
+         </mrow>
+        </mrow>
+        <annotation encoding="StarMath 5.0">E = m cdot c^2</annotation>
+       </semantics>
+      </math>
+     </draw:object><draw:image>
+      <office:binary-data>VkNMTVRGAQAxAAAAAAAAAAEAGwAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAY0GAAAQ
+       AgAAMgAAAJYAAQACAAAACQCLAAEAAgAAAP//gQABABAAAAAAAAAAAAAAAIwGAAAPAgAAlQAB
+       AAQAAAAAAAAAlgABAAIAAAAJAIsAAQACAAAAHwCKAAEARwAAAAUAQQAAAA8AVGltZXMgTmV3
+       IFJvbWFuAAAAAAAApgEAAAAAAwAAAAUAAAAAAAIA/wMAAAAAAAAAAAD/AwAAAAAAAAAAAAAA
+       iAABAAIAAAABAIcAAQAFAAAA/////wCGAAEABAAAAAAAAAByAAIAFwAAAIQAAADCAQAAAQBF
+       AgEAAAAAAQABAEUAjAABAAAAAACLAAEAAgAAAB8AigABAEIAAAAFADwAAAAKAE9wZW5TeW1i
+       b2wAAAAAAACmAQAA//8AAAAABQAAAAAAAAD/AwAAAAAAAAAAAP8DAAAAAAAAAAAAAACIAAEA
+       AgAAAAEAhwABAAUAAAD/////AIYAAQAEAAAAAAAAAHIAAgAaAAAApwEAAMIBAAABAAAAPQBP
+       AQAAAAABAAEAPQCMAAEAAAAAAIsAAQACAAAAHwCKAAEARwAAAAUAQQAAAA8AVGltZXMgTmV3
+       IFJvbWFuAAAAAAAApgEAAAAAAwAAAAUAAAAAAAIA/wMAAAAAAAAAAAD/AwAAAAAAAAAAAAAA
+       iAABAAIAAAABAIcAAQAFAAAA/////wCGAAEABAAAAAAAAAByAAIAFwAAABoDAADCAQAAAQBt
+       MQEAAAAAAQABAG0AjAABAAAAAACLAAEAAgAAAB8AigABAEIAAAAFADwAAAAKAE9wZW5TeW1i
+       b2wAAAAAAACmAQAA//8AAAAABQAAAAAAAAD/AwAAAAAAAAAAAP8DAAAAAAAAAAAAAACIAAEA
+       AgAAAAEAhwABAAUAAAD/////AIYAAQAEAAAAAAAAAHIAAgAaAAAACAQAAMIBAAABAAAAxSL0
+       AAAAAAABAAEAxSKMAAEAAAAAAIsAAQACAAAAHwCKAAEARwAAAAUAQQAAAA8AVGltZXMgTmV3
+       IFJvbWFuAAAAAAAApgEAAAAAAwAAAAUAAAAAAAIA/wMAAAAAAAAAAAD/AwAAAAAAAAAAAAAA
+       iAABAAIAAAABAIcAAQAFAAAA/////wCGAAEABAAAAAAAAAByAAIAFwAAANwEAADCAQAAAQBj
+       uwAAAAAAAQABAGMAjAABAAAAAACLAAEAAgAAAB8AigABAEcAAAAFAEEAAAAPAFRpbWVzIE5l
+       dyBSb21hbgAAAAAAAP0AAAAAAAMAAAAFAAAAAAAAAP8DAAAAAAAAAAAA/wMAAAAAAAAAAAAA
+       AIgAAQACAAAAAQCHAAEABQAAAP////8AhgABAAQAAAAAAAAAcgACABcAAACvBQAA7gAAAAEA
+       Mn8AAAAAAAEAAQAyAIwAAQAAAAAAlQABAAQAAAAAAAAAlgABAAIAAAAJAIwAAQAAAAAA
+      </office:binary-data>
+     </draw:image>
+    </draw:frame></text:p>
+  </office:text>
+ </office:body>
+</office:document>
diff --git a/test/odt/fodt/image.fodt b/test/odt/fodt/image.fodt
new file mode 100644
--- /dev/null
+++ b/test/odt/fodt/image.fodt
@@ -0,0 +1,793 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:initial-creator>Martin Linnemann</meta:initial-creator><meta:creation-date>2015-03-01T17:26:12.31</meta:creation-date><dc:date>2015-03-01T18:20:30.92</dc:date><dc:creator>Martin Linnemann</dc:creator><meta:editing-duration>PT10M3S</meta:editing-duration><meta:editing-cycles>2</meta:editing-cycles><meta:generator>LibreOffice/24.8.7.2$Linux_AARCH64 LibreOffice_project/480$Build-2</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="1" meta:object-count="0" meta:page-count="1" meta:paragraph-count="0" meta:word-count="0" meta:character-count="0"/></office:meta>
+ <office:font-face-decls>
+  <style:font-face style:name="Arial" svg:font-family="Arial" style:font-family-generic="swiss" style:font-pitch="variable"/>
+  <style:font-face style:name="Calibri" svg:font-family="Calibri" style:font-family-generic="swiss"/>
+  <style:font-face style:name="Lucida Sans Unicode" svg:font-family="&apos;Lucida Sans Unicode&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="Mangal" svg:font-family="Mangal"/>
+  <style:font-face style:name="Mangal1" svg:font-family="Mangal" style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="OpenSymbol" svg:font-family="OpenSymbol"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="graphic">
+   <style:graphic-properties svg:stroke-color="#808080" draw:fill-color="#cfe7f5" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+   <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0in" style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Calibri" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Lucida Sans Unicode" style:font-size-asian="12pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Mangal1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+  </style:default-style>
+  <style:default-style style:family="paragraph">
+   <style:paragraph-properties fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Calibri" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Lucida Sans Unicode" style:font-size-asian="12pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Mangal1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:default-style>
+  <style:default-style style:family="table">
+   <style:table-properties table:border-model="collapsing"/>
+  </style:default-style>
+  <style:default-style style:family="table-row">
+   <style:table-row-properties fo:keep-together="auto"/>
+  </style:default-style>
+  <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+  <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+   <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" fo:keep-with-next="always"/>
+   <style:text-properties style:font-name="Arial" fo:font-family="Arial" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="Lucida Sans Unicode" style:font-family-asian="&apos;Lucida Sans Unicode&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Mangal1" style:font-family-complex="Mangal" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+  </style:style>
+  <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+   <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0835in" style:contextual-spacing="false"/>
+  </style:style>
+  <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
+   <style:text-properties style:font-name="Calibri" fo:font-family="Calibri" style:font-family-generic="swiss" style:font-name-complex="Mangal" style:font-family-complex="Mangal"/>
+  </style:style>
+  <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties style:font-name="Calibri" fo:font-family="Calibri" style:font-family-generic="swiss" fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="Mangal" style:font-family-complex="Mangal" style:font-size-complex="12pt" style:font-style-complex="italic"/>
+  </style:style>
+  <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
+   <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+   <style:text-properties style:font-name="Calibri" fo:font-family="Calibri" style:font-family-generic="swiss" style:font-name-complex="Mangal" style:font-family-complex="Mangal"/>
+  </style:style>
+  <style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text">
+   <style:text-properties fo:font-size="115%" fo:font-weight="bold" style:font-size-asian="115%" style:font-weight-asian="bold" style:font-size-complex="115%" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text">
+   <style:text-properties fo:font-size="14pt" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="14pt" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-style-complex="italic" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+  </style:style>
+  <style:style style:name="Table_20_Heading" style:display-name="Table Heading" style:family="paragraph" style:parent-style-name="Table_20_Contents" style:class="extra">
+   <style:paragraph-properties fo:text-align="center" style:justify-single-word="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Footnote" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties fo:margin-left="0.1965in" fo:margin-right="0in" fo:text-indent="-0.1965in" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
+  </style:style>
+  <style:style style:name="Index_20_Heading" style:display-name="Index Heading" style:family="paragraph" style:parent-style-name="Heading" style:class="index">
+   <style:paragraph-properties fo:margin-left="0in" fo:text-indent="0in" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="16pt" style:font-weight-asian="bold" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Bibliography_20_Heading" style:display-name="Bibliography Heading" style:family="paragraph" style:parent-style-name="Heading" style:class="index">
+   <style:paragraph-properties fo:margin-left="0in" fo:margin-right="0in" fo:text-indent="0in" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="16pt" style:font-weight-asian="bold" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Bibliography_20_1" style:display-name="Bibliography 1" style:family="paragraph" style:parent-style-name="Index" style:class="index">
+   <style:paragraph-properties fo:margin-left="0in" fo:margin-right="0in" fo:text-indent="0in" style:auto-text-indent="false">
+    <style:tab-stops>
+     <style:tab-stop style:position="6.6929in" style:type="right" style:leader-style="dotted" style:leader-text="."/>
+    </style:tab-stops>
+   </style:paragraph-properties>
+  </style:style>
+  <style:style style:name="Endnote" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties fo:margin-left="0.1965in" fo:margin-right="0in" fo:text-indent="-0.1965in" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
+  </style:style>
+  <style:style style:name="Bullet_20_Symbols" style:display-name="Bullet Symbols" style:family="text">
+   <style:text-properties style:font-name="OpenSymbol" fo:font-family="OpenSymbol" style:font-name-asian="OpenSymbol" style:font-family-asian="OpenSymbol" style:font-name-complex="OpenSymbol" style:font-family-complex="OpenSymbol"/>
+  </style:style>
+  <style:style style:name="Numbering_20_Symbols" style:display-name="Numbering Symbols" style:family="text"/>
+  <style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">
+   <style:text-properties fo:color="#000080" loext:opacity="100%" fo:language="zxx" fo:country="none" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/>
+  </style:style>
+  <style:style style:name="Footnote_20_Symbol" style:display-name="Footnote Symbol" style:family="text"/>
+  <style:style style:name="Footnote_20_anchor" style:display-name="Footnote anchor" style:family="text">
+   <style:text-properties style:text-position="super 58%"/>
+  </style:style>
+  <style:style style:name="Endnote_20_Symbol" style:display-name="Endnote Symbol" style:family="text"/>
+  <style:style style:name="Endnote_20_anchor" style:display-name="Endnote anchor" style:family="text">
+   <style:text-properties style:text-position="super 58%"/>
+  </style:style>
+  <style:style style:name="OLE" style:family="graphic">
+   <style:graphic-properties text:anchor-type="paragraph" svg:x="0in" svg:y="0in" style:wrap="none" style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph" draw:fill="none"/>
+  </style:style>
+  <style:style style:name="Formula" style:family="graphic">
+   <style:graphic-properties text:anchor-type="as-char" svg:y="0in" fo:margin-left="0.0791in" fo:margin-right="0.0791in" style:vertical-pos="middle" style:vertical-rel="text" draw:fill="none"/>
+  </style:style>
+  <style:style style:name="Graphics" style:family="graphic">
+   <style:graphic-properties text:anchor-type="paragraph" svg:x="0in" svg:y="0in" style:wrap="none" style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph" draw:fill="none"/>
+  </style:style>
+  <text:outline-style style:name="Outline">
+   <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.3in" fo:text-indent="-0.3in" fo:margin-left="0.3in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.4in" fo:text-indent="-0.4in" fo:margin-left="0.4in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.5in" fo:text-indent="-0.5in" fo:margin-left="0.5in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.6in" fo:text-indent="-0.6in" fo:margin-left="0.6in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7in" fo:text-indent="-0.7in" fo:margin-left="0.7in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.8in" fo:text-indent="-0.8in" fo:margin-left="0.8in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.9in" fo:text-indent="-0.9in" fo:margin-left="0.9in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1in" fo:text-indent="-1in" fo:margin-left="1in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.1in" fo:text-indent="-1.1in" fo:margin-left="1.1in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.2in" fo:text-indent="-1.2in" fo:margin-left="1.2in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+  </text:outline-style>
+  <text:list-style style:name="Numbering_20_1" style:display-name="Numbering 1">
+   <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.1965in" fo:text-indent="-0.1965in" fo:margin-left="0.1965in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%2%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.3937in" fo:text-indent="-0.1965in" fo:margin-left="0.3937in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.5902in" fo:text-indent="-0.1965in" fo:margin-left="0.5902in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7874in" fo:text-indent="-0.1965in" fo:margin-left="0.7874in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.9839in" fo:text-indent="-0.1965in" fo:margin-left="0.9839in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.1811in" fo:text-indent="-0.1965in" fo:margin-left="1.1811in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.378in" fo:text-indent="-0.1965in" fo:margin-left="1.378in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.5752in" fo:text-indent="-0.1965in" fo:margin-left="1.5752in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.7717in" fo:text-indent="-0.1965in" fo:margin-left="1.7717in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.9689in" fo:text-indent="-0.1965in" fo:margin-left="1.9689in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+  </text:list-style>
+  <text:list-style style:name="Numbering_20_2" style:display-name="Numbering 2">
+   <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%" style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.1965in" fo:text-indent="-0.1965in" fo:margin-left="0.1965in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%2%" style:num-format="1" text:start-value="2">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.3929in" fo:text-indent="-0.1965in" fo:margin-left="0.3929in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%" style:num-format="1" text:start-value="3">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7866in" fo:text-indent="-0.3937in" fo:margin-left="0.7866in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%" style:num-format="1" text:start-value="4">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.2791in" fo:text-indent="-0.4925in" fo:margin-left="1.2791in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%" style:num-format="1" text:start-value="5">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.8693in" fo:text-indent="-0.5902in" fo:margin-left="1.8693in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%" style:num-format="1" text:start-value="6">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.5783in" fo:text-indent="-0.7091in" fo:margin-left="2.5783in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%" style:num-format="1" text:start-value="7">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.4839in" fo:text-indent="-0.9055in" fo:margin-left="3.4839in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%" style:num-format="1" text:start-value="8">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="4.5075in" fo:text-indent="-1.0236in" fo:margin-left="4.5075in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%" style:num-format="1" text:start-value="9">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.6102in" fo:text-indent="-1.1028in" fo:margin-left="5.6102in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%" style:num-format="1" text:start-value="10">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="6.8311in" fo:text-indent="-1.2209in" fo:margin-left="6.8311in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+  </text:list-style>
+  <text:list-style style:name="Numbering_20_3" style:display-name="Numbering 3">
+   <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%" style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.1811in" fo:text-indent="-1.1811in" fo:margin-left="1.1811in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%2%" style:num-format="1" text:start-value="2">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.3626in" fo:text-indent="-1.1811in" fo:margin-left="2.3626in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%" style:num-format="1" text:start-value="3">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="3.5437in" fo:text-indent="-1.1811in" fo:margin-left="3.5437in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%" style:num-format="1" text:start-value="4">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="4.7252in" fo:text-indent="-1.1811in" fo:margin-left="4.7252in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%" style:num-format="1" text:start-value="5">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="5.9063in" fo:text-indent="-1.1811in" fo:margin-left="5.9063in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%" style:num-format="1" text:start-value="6">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="7.0874in" fo:text-indent="-1.1811in" fo:margin-left="7.0874in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%" style:num-format="1" text:start-value="7">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="8.2689in" fo:text-indent="-1.1811in" fo:margin-left="8.2689in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%" style:num-format="1" text:start-value="8">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="9.45in" fo:text-indent="-1.1811in" fo:margin-left="9.45in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%" style:num-format="1" text:start-value="9">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="10.6311in" fo:text-indent="-1.1811in" fo:margin-left="10.6311in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%" style:num-format="1" text:start-value="10">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="11.8126in" fo:text-indent="-1.1811in" fo:margin-left="11.8126in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+  </text:list-style>
+  <text:list-style style:name="Numbering_20_4" style:display-name="Numbering 4">
+   <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="0.1965in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%2%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="0.3937in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="0.5902in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="0.7874in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="0.9839in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="1.1811in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="1.378in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="1.5752in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="1.7717in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%." style:num-suffix="." style:num-format="I">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.1965in" fo:margin-left="1.9689in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+  </text:list-style>
+  <text:list-style style:name="Numbering_20_5" style:display-name="Numbering 5">
+   <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%." style:num-suffix="." style:num-format="1">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.1575in" fo:text-indent="-0.1575in" fo:margin-left="0.1575in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="2" text:style-name="Numbering_20_Symbols" loext:num-list-format="%1%.%2%." style:num-suffix="." style:num-format="1" text:display-levels="2">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.4335in" fo:text-indent="-0.2563in" fo:margin-left="0.4335in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-number text:level="3" text:style-name="Numbering_20_Symbols" loext:num-list-format="%3%)" style:num-suffix=")" style:num-format="a">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.6102in" fo:text-indent="-0.1772in" fo:margin-left="0.6102in"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+   <text:list-level-style-bullet text:level="4" text:style-name="Numbering_20_Symbols" loext:num-list-format="%4%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7874in" fo:text-indent="-0.1555in" fo:margin-left="0.7874in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="5" text:style-name="Numbering_20_Symbols" loext:num-list-format="%5%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.9429in" fo:text-indent="-0.1555in" fo:margin-left="0.9429in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="6" text:style-name="Numbering_20_Symbols" loext:num-list-format="%6%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.0984in" fo:text-indent="-0.1555in" fo:margin-left="1.0984in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="7" text:style-name="Numbering_20_Symbols" loext:num-list-format="%7%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.2543in" fo:text-indent="-0.1555in" fo:margin-left="1.2543in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="8" text:style-name="Numbering_20_Symbols" loext:num-list-format="%8%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.4098in" fo:text-indent="-0.1555in" fo:margin-left="1.4098in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="9" text:style-name="Numbering_20_Symbols" loext:num-list-format="%9%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.5654in" fo:text-indent="-0.1555in" fo:margin-left="1.5654in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+   <text:list-level-style-bullet text:level="10" text:style-name="Numbering_20_Symbols" loext:num-list-format="%10%" text:bullet-char="•">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.7209in" fo:text-indent="-0.1555in" fo:margin-left="1.7209in"/>
+    </style:list-level-properties>
+    <style:text-properties style:font-name="OpenSymbol"/>
+   </text:list-level-style-bullet>
+  </text:list-style>
+  <text:notes-configuration text:note-class="footnote" text:citation-style-name="Footnote_20_Symbol" text:citation-body-style-name="Footnote_20_anchor" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+  <text:notes-configuration text:note-class="endnote" text:citation-style-name="Endnote_20_Symbol" text:citation-body-style-name="Endnote_20_anchor" text:master-page-name="Endnote" style:num-format="i" text:start-value="0"/>
+  <text:bibliography-configuration text:prefix="[" text:suffix="]" text:sort-algorithm="alphanumeric" fo:language="en" fo:country="US"/>
+  <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
+  <loext:theme loext:name="Office">
+   <loext:theme-colors loext:name="LibreOffice">
+    <loext:color loext:name="dark1" loext:color="#000000"/>
+    <loext:color loext:name="light1" loext:color="#ffffff"/>
+    <loext:color loext:name="dark2" loext:color="#000000"/>
+    <loext:color loext:name="light2" loext:color="#ffffff"/>
+    <loext:color loext:name="accent1" loext:color="#18a303"/>
+    <loext:color loext:name="accent2" loext:color="#0369a3"/>
+    <loext:color loext:name="accent3" loext:color="#a33e03"/>
+    <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+    <loext:color loext:name="accent5" loext:color="#c99c00"/>
+    <loext:color loext:name="accent6" loext:color="#c9211e"/>
+    <loext:color loext:name="hyperlink" loext:color="#0000ee"/>
+    <loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
+   </loext:theme-colors>
+  </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
+   <style:graphic-properties style:horizontal-pos="center" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0in, 0in, 0in, 0in)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in">
+    <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+   </style:page-layout-properties>
+   <style:header-style/>
+   <style:footer-style/>
+  </style:page-layout>
+  <style:page-layout style:name="pm2">
+   <style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in">
+    <style:footnote-sep style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+   </style:page-layout-properties>
+   <style:header-style/>
+   <style:footer-style/>
+  </style:page-layout>
+  <style:style style:name="dp1" style:family="drawing-page">
+   <style:drawing-page-properties draw:background-size="full"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:body>
+  <office:text>
+   <text:variable-decls>
+    <text:variable-decl office:value-type="float" text:name="a"/>
+    <text:variable-decl office:value-type="float" text:name="b"/>
+   </text:variable-decls>
+   <text:sequence-decls>
+    <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+   </text:sequence-decls>
+   <text:p text:style-name="Standard"><draw:frame draw:style-name="fr1" draw:name="Grafik1" text:anchor-type="paragraph" svg:width="2.0835in" svg:height="2.0835in" draw:z-index="0"><draw:image draw:mime-type="image/jpeg">
+      <office:binary-data>/9j/4AAQSkZJRgABAQEAeAB4AAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQY
+       GBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYa
+       KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAAR
+       CAD6APoDAREAAhEBAxEB/8QAHAAAAAcBAQAAAAAAAAAAAAAAAQIDBAUGBwAI/8QAPhAAAgED
+       AwIEBAQFAgUFAAMAAQIDAAQRBRIhBjETIkFRB2FxgRQykaEjQlKxwRXwFjNictEIJEPh8SZT
+       gv/EABcBAQEBAQAAAAAAAAAAAAAAAAABAgT/xAAbEQEBAQEAAwEAAAAAAAAAAAAAARECEiEx
+       Qf/aAAwDAQACEQMRAD8A2t0YoQpwT2qVzMV+N3UHgrDY2eoM0y58VEbgfp9K1yMRmnuJ5h40
+       jyYHGSeKrWE8u2QAApOMdqGCsmT8h70TAJwMAZx249aKBy4c9vTNUC0zDCgmmmG7Ockjkj1P
+       rUTAjcy5XP0ouCgHae4IomOJHhgIc55PHY0Uk5IXLMcUBQ27n96JYO2MYLebHtRBA7BcMx29
+       sdxQJqwZRtIP+BQKpjHHc+xzigNGoAO/k+nPAoAYlee5oBiGeWySO9AJCgY5PHagFCADzj2G
+       aA2N2TkjA/U0HMwbPPeiyBLDfkkj04FCl1cBMgn6URwYFGySR6D2oAeQDAxnHGKAhU4IbGc+
+       tFwnwDj9aK7f8v2oNu+IHxNvJdXmt9EmKWSqArA/mPvxUxMZNe3Ml1dvNcMzSSEsxPOferJh
+       ht/OWyAPc0UfdgDcuM8n50AMCykZFARsngcY/egTcbjnJz9O9AB2kZGSQOcUCX8x83bntQCM
+       ruJ4B7D1oCyOGzxtJ9M80CAdg5UjFE0aFJrghLeNpHY4IRdx/QUNWCw6D6q1EZttEvirHAZ4
+       ig/U4qw1b9H+CHVN3Mq6hJaWMJ5ZjJ4hA/7R3P3q3ET+pf8Ap/lWNm03XkkkA8qTW+3PHupP
+       9qxopV78G+s7VSV0+OcAn/kzqSfscVvIKzqPTWu6XKE1LSL+Bhz5oDg/cd6lEZzGwLrtPqrA
+       8frUCJfcw9gfegUjZsEAffNADyHt78UAjCjzDJxRcO5Pw3gwCGOVJQp8ZncMGOeNoxwMY96G
+       CbQffFFcUXKjDDt2NEo+N3yyM5z3okKuqJgIzONoJyuMGi4QfGcqSfXBoYHJx659qKIRnnsf
+       UGgJn/poJYoTIGLY+eDzQFlQK2G/KCTmgbspfO0qce/agPGcR7nHf9vnQFfBPlOc88Gg7uuc
+       c/M0Bd208YJJweKAYrea4kKQICRGW5IUYUZJ570DYqcknt3FE0VuVyDzj1oamOlulda6puvC
+       0a0eZVIWSbtGn1Y1NNbX0x8ENH0qL8X1NdtqDoNxiQbIh8u+WpqL70Tc6fcxypouiRadbW8h
+       hLFFXcB7Edz+tNFvEZxkmmgShbA9PlUA+Hgg/wBqDgmBkd6ArJuJBGR7VdEdqWgaVqMfh6hp
+       9pcLj/5Ig2KaKJrvwW6S1EFoLaWwmPIe2fAz81ORTRm3UfwI1mzBbRL+K/ReyS/w3x/b+1We
+       xmev9O6xoE2zWdOubUDszr5T9G7H9auCJj2n3PPrUXTlGBB2kYx96GlQMjJJHuRRXBgDgk8D
+       tRKH8w4OfYA0SUlIMsFXJ4oujHH8ufnRRGOSNoJNAeFC77F2jPucfvQFEqgY3nj/AKaCUY58
+       wwq54AoCzOmVMke9QeRnGR7ZoEIF7pnaTk49KDpSSwQntQJsGKjgggZ9uDQc4OOe1Am2UCkH
+       OR7dqA8t/cSW8MEkrGGEsUTPCk4zj9KJT3pzQtS6m1aPT9Jh8SVxlmJwqL/UfYURuuhfArR7
+       f8NLrF1cXciKDJCrbI2b7c4+9NGtaRptrpdqltYW0VtAn5Y41wBUodvGjqUdQyn0YZqAIreO
+       BFSFFRF7BQAKA1xcRwKplcJuOBn1NAR7y2ikWMzoZnGVQHJNAuQcD3oBKkD2FBy8jnvQFxnj
+       jmg4rxwKBMqCBtPNA3vbCC+tngvYo54HGGSRQQR9DV0Y91n8DNOvFkuOmZmsrk5PgSNuiY98
+       D1X+1XRhWu6DqWgX72er2j2069t/ZvmD2IoGG7jbnj1FFlB224PB+VClN4DYJHyAojmPGCck
+       8cetCAxgjPp6UaAGKtx6+9ATAXO7nFBw8HHLN+goJhBuj2FeAcnmgNazW8U0vjweODGyqpYr
+       sYjytx3x3oGa5LEEjH9XvQGlgmjjMmQq4HBPfPYgevagG5nhe3tkFuInQHxJQTmQntn0wKBK
+       TlAeDx60DSY+U9zn+mgsnQvROr9Y3W2xi8KxV8SXUnCrjvj1Y/IUR6c6A6H03o6wMVgrSXMo
+       BmuX/NIf8Djt/eiLfjJwO9ZBiOfmKDhktzQAzYBLZ8oyaDF+rOptVv8AUjNZL4tjA/lT+kr3
+       wvqTQX/pi3Y+DqFxKXurmFWAaPaVzg4I/b0oHlxqV7penRTXFu93dPLsESYB2k8n7CgnradL
+       q1WaIOFI/K42sPkRQCg3Kcd6Dgp3d6AdrGg5VxnjmgKWB8uQGxnFAUgKuSefSghuqNC0jXbA
+       WGtxQyJKdsYc4YMf6T6GtDzR8S/hnqfSUz3NvuvNILYSZR5o+ezj/Pb6UGfLzyD/AJoFFySQ
+       VBHpQDJ5kGByPahAbWxn5+po0OF3D+XPtQJsNwOe+aAuygmMkebgHnHFALHYpJwSeGz2oGpO
+       JWAI49BQEZlYAHkg4oARVOMvtBIJJ7AUAX6xxSOsUgmjViFcKRuHviiVfvhT8NZuqpk1LVFe
+       HRkPlHZpznsP+n50qPS+mWVppdnFa2cEcFtGu1I41ChR8qyHVxK8cLPDD4kgGVQHBNAa0maa
+       BJGTYzDJXOcUCy5JOaA2OMfoaArkheM7vlQNYNOtoWLJCgLHJwo5NApPKLaNpGRQB6j2oGmn
+       RvcyNd3O/DkeErLhkWgklIdCyZOCRzxzQEeRxhdpUnncBkD5UCxXjJ7+tAlctMsIMLohz5mc
+       ZAH09aBQYdQwyAeaAuA7MAQxHH0oG1481nbGVInuWU5Kr+bHrgepoKB1u+o6jqlvBH05NevE
+       BPBK0pQR4I4BHZj+1Bb9IS7lsFtNWtYwDGFYB/EXHqpJ7/WtQYx8VfhGbdZtV6Uh8gy81mpy
+       R6koPb5UGKY4YkeYd88fbFAI5AC98c5oQBb+U9+9GnN5RgDgjOPWgAN3yMfWgAqc91/UUD2R
+       SSRg9+49KCR6e0WfX9WS0icRwgb55WOFijH5nP0FBYNRi6dSR7HRNPmu0hOW1GaXaZMdwBwA
+       P3oynE0XRYrFtV02wS4ECj8dp1wcsE7eJEf39qlFZ616ZttPu7Kbp9Zbi0vYzNCcgjHqoHuK
+       sEp8LPh7P1PqjXerxywaXaviRSu1pWH8g+XuflQemIIY7S3SK3hVIo12pGoAAA7AClEL1N1R
+       H0/oTalcwx+IACLaSQKx59Ppmshv0D1jH1ZbTubU27xkkAnKsuSMg/UUFluLlLaJXETyecKA
+       g554zigXiubeRnSKeJ5FOGVXBIPsaBLULoWkIfw3kYsAqIOSTQJMbpm3oqlmwACeF9yfn+1A
+       +Bx34oE5IY5P+YFbnPIzQKAckHuRQCAQOO1AL8r9KDhkZOT9M8UCcrxgAyYJzwD70CT3Itxm
+       8kgi3fly+P7/AOKA9pskhEkZysnOfeg6RH8w3tgjAHtQRZ1uystSg0m5eRJ2UbHceV8fP3oJ
+       kBSAVII9xQFdSRwKDDvjN8L/AMSJte6chxcgFrm1QcSf9aj39x61YMH8Q+CkfhqpQncxBDH5
+       H6VRwXJ/Ke1Am2QchuMYOaNFSAVznB9qAm8f10D2RmX8jDHP3oLbebtA6ej0m2LrfX6LcX7I
+       Mskf8kf6HcffIoG8yTadZxSTxCK3kRZUwSFfkruIJ78GhiS6Y1OS3160uZJFWO5bwZtxzuQ8
+       bcfPNMZXvo2wsLnQ9R0q/maJNNv5Yo3bjCuMAHPzqA2jdUan0lF0/ZXcElxp9zE+5WVd/DE7
+       1IPPB7H2po1bSNXsdYthLp1ykyEcj+ZT817ioITrnoux6vs1gv5JYnjz4ckZ/Ln5etA+6N0B
+       emdBttMina4WEFfFdQpIJJ7D60E5I4Vo9qnnsQO1A3k0yzeTxhCizZJ3qNpz9RQO449igMSx
+       HGW5NAIwBtUAUAMORkfegMhG3jtQD8+fvQGXJz7UAHuRQA5YDI5FB0qCQA5yaCs2/SFit/Jd
+       3AmvJ2bO64ctt5zwD2oLMilVAUDgcAelAJLbhgZz3oGN9HPIYmhtrWRw2czjt7Y+dA+h3mJS
+       67W9gc0AvuLYANADpkZABHY85oPOnxy+Hx06Z+odGjC2jt/7qBRwjH+cY9D6/OrKMebcceHw
+       faqCYIyDgZ96GhHOFJI4/WjQpXnsaCz9J6fDqGvRC8OLO3Vri5PB/hqMkfc4H3oDT3Uupapc
+       XrKS9zISgDdhnAGPbsKC5aLLBHq9p01c6bbagPE23kpJYhmz5IySAAMj6nNGdRnT2lu3V9va
+       QQrJDHfCMFj5kAfufsMUFogu5H0jrLUYXK+Lq0aRse/lf/8AOKlFfudagvbnQpNQRmtILydC
+       wPdCQcgMOMZFQanPoeiawBd9M6s9jeKPK1vKQp+RFA4septa6fuFtuqbRrmzx5b+BAdo927A
+       +vsflQXfTr2z1O3W5025juIW/mjOR9KAZI914khaRNo4XdwT9KAl3b2+oWpjMoZWbOVfnI9s
+       UCrXUNssUU8w3sQoJH5jQLvwQQC3NAKvuUPtK54waDg23v6UA7weBnNAIOBigMr+hoOjdZQd
+       hBx3waAVG0Z7UBWfAOQSflQChyNxBAxQRutarb6bHALi9trSW4kEcJnGd7ewFA/j8QEK/IA/
+       MBjmgWDDBB7igj9dupLTTbiaHZ4oQ7A7bQW9ATVgwXSNV6onl8azW6t45pWdxHIxWA/zNtz7
+       A8Glg2S1u7fX+nt0J/H2c4MMhmQoW9GBUjj60g8sfEHpebpDqi4sHLG2Y77eQ486E8fcdvtV
+       FekGW4UfegKVAAKgnFGhuDzxQXbpDTZF6a13UnUqrCOzQ5wGZmXIJ+lE0ppkEK6nJcRWcTW9
+       hA08iKcjcowpye/mxQ0+6VRbC/jvLm48L8LG9y8pIOXxkDnuSTipqHXQMng3es9S3fhn8Dbv
+       cZI5Mr/lH700dc3Dad8NtPs4nU6jeXD6nMCwBRF5XOfU8YHrTNJFF1X8RawW1jc4GxTKNrZB
+       385yPkBTFw1stSu7Ni9tPLGSQfK5Aphi8J8UNUm6fn0u72yvJ5fGbuF/39aYYtGgadp9/axX
+       nRetzaXqnhqZI3bEcj4547Ak/X6UxFisPiXe6NMdO65057eQAr+LhUlHHbOPX07UwWXpQ6Bq
+       Mo1LpgW0sioVI8Qgxk+684qC028M5890Y3kHKbUwF+lA4LDOzu2M4FAOG3DaoI9cntQdJxzQ
+       EyR259f/AKoGl5fSQRFo7ZpB/MhYIR9zxQdayyXKb7gqox5Yo2yB9WHc0DPUIWnhWKxkuYFR
+       s5gcKWbPY59KBkx6isVeSGW31JNwHhyOUkA+o8v9qCfjkMo/LJFKqBmRvSgc2swnRyFcYODu
+       XGfpQMtRsLK8vYJL+wjuGiUtHK6hghz6Z7H6UEmCsig84I9RigiruC9t0DaaVmIIHhTOQMeu
+       GwT9qCJ1ywv9T0U29xFFiaVBJGHz5M5ODgYPY/arKJPTtLW1t44i7SKq48w8x+ZPrTRJoipG
+       FQAAdgKgzX47dMJrXSrXkUe6807MykDkp/MP8/aro80FQyZ+tUJ7hvH0x270XQ7KGtXvIk0T
+       4c9P2bIhkvpnvJVfjIxhf7qftRDXpu0/1DpzXltUlkvmWMBI+2zdnn64oYa6yX0XTm0i4jQ3
+       t6wmuV53xov5UPpyeeKyLbpFtZ6Xpmn6TqNq7/ic6pqQRR/DVf8Alq2fTOP0FXBnXU+ox32o
+       3lzeW+JrxlMXHKR9wfbJ/tVWK5f3AnaAjafCTwwcY4BOM/qKKSjA4Dg8j37UHZKkE5P0olSF
+       lcLDdJPbTNBOigjxOVZu3+80Rbbnrq9l0t9I6isRd2rgKpPlZMdyre9An07oupoh1zo2+lea
+       2fMlr+WZFx7ZwwqWDVug/ihDq7R6b1EPwmpMNokPlVj8xng1BqEUe1EAJOMDOc5oDSxq6YYe
+       uaAJF4oCBUQ7mJ45zQHYB14wR86AVjBXyjge1AEcRTHlA9hQE8kbgEohJ5yQM0ETHNqMOr3I
+       ZQ9tIMQyEjKt7D3FBLqywRPJKTuxlj3zQI3Ut14e+yhWRj28Q7RjH60EfpF3rU/jLqFrHbS4
+       /hqpJXH19aCRa8jgiVr1xGwXzYyf99qA9tc29/aRXFnKs1vINyOO2KByoxwe9AYocHGKBvdw
+       LcWzxSLuR1KuD6gjBoPHXWujt071Nf6YSSkUnkJ4yp5H7f2rQgWAA3Y+1An4j/1t+tBrHxKu
+       YS+gx24LRx6ZFtI/lz60FY0+/v8ASphNpd68EpXY5AGNvzFF1YOirZbzVrvX9dkNxZWH8eea
+       Y5Lyj8qj7kcVlETqOqXd/HrPUNzcNE16Tbwxf1JkEgD2AA/etBte9R2Oq2cv+p6XHJfBFjgm
+       jkMaRgAAEqO5o1FWfbgjsR8+9AlI5CgEggeoNAq0iug8uD7g80KKmCcZ7fPmjJzJfT/hWtjJ
+       vhOPK/OOe49u9A96X1W90/VrRtNkkSfxQF8I5yScdvX6UGidSLpfVFzcvbRiy6kgZBGysFW7
+       B9T7HHNSjU+o9S1iz0e2uNLmX8RYxJ+KgYeVwVGTn5d6gjug/iU3UOt/6TewQpP59skL5B29
+       x/8AYoNHPB78Ggb2l3bXO78PKsoyVyvIBHBFAoSkbfyhn4GfWgTnmWFN7ybAvc4Jz9hQRdx1
+       dp0S3Dw+JJHbDdPIUZUjX3yRz9Bmrgzbqb4x9Oxho4bB751O5HPkXPsc80wQHT/xrJ1IHUbG
+       KO0kdRiBiAgz+YjnsPpTKN/tLy3vLOK5t5klt5F3LKhyCPemAYLuK5XMDEj1OCP71ArGWLMP
+       T0oIbU7h11u2t49OllWWNm/FIRsjI4AI/egfQ2ktpbww2XgxoDl9wJ49cUCHUGv2GixM13Mk
+       cm0squwUH5/SrgxDW/jFcXOteHb3otrKEEiRISRM3zGc49v1qDT+gfiBpvV7y2unxTxywRhz
+       4ozuHbOR2+9Bmf8A6kNIEWpaZqiooEqtBIR6kHI/atfRjDEt3AKjgVQjug9j+lQWh72e/htT
+       OzyeCnhHPomeMYoJvQum7vVD47K9jpsQBkvZ5NoAHcgUAa7rKamE0Lp9Xh0G1OZZTwZSO8jn
+       9cCsivdS38F9cJDZIY7G2URxKe5x/MT7nNaEKrENwAFPPlosFwS2cd/cc0UlIm3JOeKDo2LH
+       +UA0SjgDk98URzPiJ2449e/NAbS7v8PdpKkpikQ5WQLkqccGgmYNQmXWLeQLG9wVRQVPlcj+
+       Yn3xQa98OviAjz3WjdXSpFdliEuJCNjDtsJ7enepRdel+kdL0rqOTVdIsoYklV1dixO3nunp
+       g9jUCnU3WMeka5b2EUcl3JInmigQs6ZPlJAHY8+vpQP9O1m3nthNo0cTwM2JDwoVj6H5gd6C
+       bhtUiVn8TcXO4ktkZ+We1BAf8Z6fZ2uqXWpyxQrbStGseQzMB2IA961B59+IHXmodXal+HsP
+       Fh04HbHCo25+bY/zQWv4f/CCxvII73qC8iuXYb1tYZeF9txHf6U3BatX+DvSl86x6cr2dwjb
+       nEUmcj6Enj6U8hLdJdEX/SmowJp2tTT6Oc+La3HO0442+3NBf1LmRUjjQAfmc+nyHvWQockY
+       yQcY3CgaabaPZxGNnaUFi3mPb6f+KA2q3RstNurnBxDE0mPfCk1YPMemaP1L8RtYN9fJPc2a
+       MUaVmCKg54H0z6VRYendf6Z6T1W56a6j6fgfwJyguhGJmPzbIzjHtSjTn0zSunbi01fSkt9O
+       s5GAmWNCDOGxtXb6HnNZEZ8etOF90DPKFy1rKk3zAzg/3rXI8u7zvOTg4zVoTLDJ81QWDTb2
+       SwuvFgcrkbXwM5H0PFGqsjpd6+kcT61Nc2ieb8OikFc/9PA+WfSjKA1nWBzpFlZ/hLWM4KH8
+       zsPVj6mghN4IyQRk5NGo5BkFmyAfSgVjChdpGO/FAXYpOHLBe/FAQqoBJbA9sUBGxgtgEj/e
+       aCf6DGjt1TZf8RNGumKS7mQZQkDIB+WaMrf8Ub/ovV7V20JIYL62K4khhCLOCcEcAdu9BmCu
+       EQvxvyFUg42+v+/rQaj0zax/EXRY9Nns0t9TtM+BqCKAjEclXA98jn+1Si7Cz6u6O0tLjTrx
+       Lu2tQJJrDwcKE/m2M2SfeoLrpupDV9Mh1OytUS2vIN8m4BZQf6T7+vPpj50GfdK9L6rJqk1y
+       1y0elRDKRqdjHHoyDhjx39e9BZr7fagW0j3kul3iETRqHkeF8ZBUjkZIxjtk5rQ86dW6r+O1
+       OcW0UtvaRsY4oWfLKBxz7k/5NA46P6X1rqS6WPS7V9v88rAqi/f3oN46X6C1DSotkus+BIwK
+       l8hn2+3PapROXPT2t20bPY6kJ5UGYmbIfIHGW5z68VBI6DrzzWSrrAjtrwFUbDja5OBlfuaC
+       bluJLeNwIpLiVF3bVXAP0Pv8qBxLO8cYcW7vnuqkAigNFKs8CyxlwG/lcYI+1A31ayF/pt1a
+       uSFmiaM/LIxVgwfoO413o3qqfSLyUSwodogAyZVGcbPTPr71aNDvendJ6wtbu7Fi1lezK0bS
+       4VZMjtnFZE0bC5u9Jh0qRAr2yw4uWx59vBI44PegN1tpbap0lqOk2sipLPB4aFsnHbBNOR5A
+       1exFhqFxbeKkngyMhdOxIPcVuhiZFz/zBUEwcKvYnP6fWi0+6chjn6h062uATFLcRrIMnzAs
+       O9EehNR+GvTV3GUh0+O2YsGaWHIf9amjIfib0no3S0VtFY3M000zMzLJtLKvvkenyx96oz0r
+       wNjA8cj2osFLbVAbOc9jRQiXOAwxnj3oBlAxwDj37UDY+vHOQeTQBIdqjcPMfnQwJclWyBgC
+       jJBFeefw4VaVycBUGST2wAKD0L8H9C1rSIILjWLSCytY1lZASVnlL4PI/wD8+vvUo1uwbxI5
+       GkjdVc7isvOBjtj2qBWKFZiQ8CJCB5FHYj5jHFArDbQ20ey3RY1HOAOPsKCH1u61CPSLt9Mt
+       lXUHUrbCbJBbPdtvYetXR5T1y2udD6lni1ErJdJLvlK4wWOCePvVgsV/8Sr67UW1vA0NiowI
+       onMe4+7FeT9ARQRmodWa9EYpPBhs1Tygw26rk9xknkn70EjonxZ17TXjAeKTkZ3L+YZ7N8vp
+       SjX+lOpNM6umgkMG3EgBV1DYbG4kewz2NZGkC43CP8MPFBONysMAD50DaHVH8S6N1a+BaxMU
+       jd280pA5wPb296DrXWLK9WNoJdtwybxDKPDcAnHIoJBifTBzzmgaz2UFzPFNNbwvLCcxuygl
+       T7igdRRKg8qAZ5JAAzQEnuYoHiSWQIZW2ID/ADH2H6UERr12BY6hueIQJaO7SK/nHfnHtx3p
+       yPGWoN4jynuCfU963RF+DL/UtQWTkjaWY/8ATnHFGql+j1VerdJY8r+KiJz/ANwoy9C/EjqS
+       bpbRY723RJC8ojIcZ4IJ/wAVkecer9en1+9FzeLCCq4URjgDP7mtLhteadBY2kMczyHUpcO0
+       YxtiUjgH/q9celAiLy1kjCX1ruyMLNGdrj0+h+lE0+t+kNQltJ7yKS3jgiTxUFw/hySp7qp7
+       0NV6YEBgWUNjBoaKeAODnHrRoVgDnBP0ozpxZ2f4y5trVeGuJFiBPpk4zQep9C0LTembS30f
+       QbWP8ZsDyTugZgf6ix9fYZpbgmbXSmXULaa6kMzpltzcjJ//AGpaLCY1CDsF74PrUCgHY0Hb
+       Sx7Z96BGUfxB2xjtQZ11t0Tb6jNfyw2wM18gV5AcBdpzyPnV0Yp1F0o/TEczXjXaTOQYpIk3
+       QlT3B9Rj0zVl0VKbVppImheUSwbsgFfXHc0De0tri/ujFYQSSyfmKopPHqaDV/g9p+padr/g
+       kSRTzKu0kZRlPLYPbOPf2pg9GWzRCMJAFxH5do4wayKX1z/G0CdzqLWRkiaTxQBLudclQvov
+       bv3oMU/4Z67uwnUAt3u1Zw42yhmx3/Ln8v0oN86L1d00i3i1UiGQIocNnEbnkqT2xgiguEbI
+       4DIysp7EHNAZnxQQ/Usksej3EsCl5EUthR5sY52/Mjigr6Qrp3R15LqEcIlmgdpFGAsY2nav
+       PJApyPJtwd8rnGBuJz6Gt0MzGSTyf0qCwSKA5ZsAjnn2otTXQYj/AOMNIDqCrXUZwf8AuGDR
+       G6fF6Gyfo6+ub0CR4EPgIScLIeAcfc1keatN0661a+S3sYTPKzAbV9B7/StLrQ/iXp9pYLp8
+       elWsUM11AzXMqt53I7g7j244oiB6W6Tn6j2TeAy2FspTeB+Z+/8AmgtnWlvpdl1Dp1pq07Ja
+       20GFQpuDHHAwPf39KDHriVTKSPOCeBnHHtQGsrG5v5pfwcTOIlMjgEeVfck0XRIreS7uUigR
+       pJXOEVe5PtRE/wBJ9HaxqvUcdhNFJp0lviaSWVcMgzxgdySeBipo9T6O8NppUJ1K4iW5KgSv
+       Iyqxb5jPH0paJm1NvKivE6Mp7MpyP1FQLRTwy58F0cjuAckfagOTtO3+Y8igMWCIWbOPlzQN
+       hNBOWEbq5Q+YKc4+tAD7JEZgQfXj0oI/VtIttXsZLW5hRopByCP/ADVlGRa78Erae63aXK1t
+       G3LAncM/Kmh10l8IZdBv4rxtTE0yggJsyoz6/P0po0zSNKEMdo9xGnjxuzkqMAEgjj7Gmial
+       jWQMgyCRyQcGoITqHT7q/a30+G2jFmwLSzl8GPBGFA9c5NBPRwJDbpHCipEi4CjtigqfWltq
+       CaG8WhNbxyzOBIs8W8FcY4+dWQQ/wtuZdIGqadrknhy2u2QyOSEZOeRngY+XvTBZZevOmhC8
+       janbqiZ53Zzj2FMFcs+sh1ZqsFrp8UkGkrlpbh+DNzhVX5Z5NQTfXyWUXSV2t+wW3EZ5I4HH
+       t61eYPI0mA5C9snFaoLsPv8AvUEk5JcA8cZG480WnOlXX4PVLO4yQ0cyP244Yf8AiiPUfUum
+       p1B0/c2O8xfi4v8AmL3UcGpgw/SujNX0Trj8PpckimOMvHO/kEg9Rjs3PpV0aFq/Qqa1ZJ/q
+       crverEqNOwGM+uMfemiVtrKbQdMNjp9rvtkhPht6mU5yT8u1BkvXg1qXUtOvddgRY1R3j2YH
+       iMvZSD27CgzSCyuNQ1KK0giL3Mz4VAOc/wDignoNNOnaHeiW8hgkku/Al2+Ziqgn09M0ETp0
+       qpqSmGKOdFcEeLwMfPHag3JLuCRtPmQWsDhNphtVAcn1w3BPFSwDdWGpX1/OYdOtbbSrlQ80
+       szHdn0GR24/c1AbWemdatLbTJdGvJIJypDQwMV3exwOPatSz9Ei/R+txy2mr3evyHV4miWID
+       hGwwyGA7nGRS2YNZAUBWYDdjGayEvxMYB3nYu4KCfU+woG93c2enWs1xcPFDCp8zEgDPzq4G
+       Ggz22saS1zZSZhkdsFePXt86YHWmySeLNDMYikZ4YNlvvUD+VARxQJqgwRQHUAAe2O1AWOFR
+       M8mDlgB37fagb6reXFt4ItLZJnZsuWfaI0Hdjwcn2FAz0nWX1i4u4xY3VpFbv4eZ1x4vGdy/
+       KgkriN2aMRlQoOW3DOR/5qwYr1P1tp2pdS3WnanKkGh24kRl2eaYgcb/AFxnnAqiv9Jno0dR
+       LJPbtdQtkNPIALaMnODsPPpSjbdK0DTbWQXui+Gsco3BU5hPP5gPT7VkU74t6PZHpq/1N5ZZ
+       bwrtRnmOwDPOFJwPsK1xR5ybudw788VaC5X2WoJRULSBpAe5PA/aiinAZnHck4A70THq/Qr2
+       3/4Y0u4lmCpLBGA7epxjH60Du+WGOBvFlFuWOFcYyCfbPrUojri6k06xX8PFNfBUJ3ltzM3s
+       cVBjfVvVXVNit5dapNDZGQGK3shjeAe7YHIwAOT3zVggNTiu+orrR4p7m+upJFR7h3TPhggD
+       aoBwRjnPH5hV0af0xotnoD3l5dWdrY2YjGLhwPEHoef996CC6m0HpuPpk3Wny2s9sJPHJ3Ae
+       I2D39T37UGU9QTDULuGPSLPwIyoVIYk2lj6/X70G2/DPp0hVudRuBLcwxhRGkeEjB9M+p96D
+       SLprVHiieaAE+YxHkke4H1qUOIBawL4uAuc8nvj71AwjRtQ1eO78QNp9odyLju/qT8uf70Gb
+       6r8SpLzryy0vp+4NxYSSCEswI2u2Rn3OOD9qC4dVamen9NlaC7tUaIFvCmnHiy4HJXJxnOeD
+       Vwed+rOvLnqSyWO4jZSru/kc7ck+30GKosXw2+KmqaDJDY3jR3OmqNoRhtZAP6SP7Ggtmt63
+       qbTJ1XLazJpslwBFblypEOAA7L2OWANS+xrnR2vW3UmjJeWp8wJSRf6WHeoJdSPMCRmgMq8D
+       mgBpNsgUIxBGSccD70DczmS8MDWoe28MN4+f588rj980CktuJZYpFdlKZ4B4OfegQ1hpIrVj
+       bsRMBhBj8x9qsHnX/hm36y1O/u9V1ddPmS6aD8OkQdyxOSe4OMmqLUvwQsYY4Xjv7m4YEEhw
+       FyMY7fXB5pRbvhp0jqfSMV7b6jqZvLGQAwxAEBDk54PuD6VkVb49a5DBpiaNaeF/FIaQDumO
+       wpzMGDEZQZ5+VboR8In1I+9QWDY6gFn7jjHpQhtJEFbAGBnijT0P8H7qPVOh47a42yNaymPB
+       OcDupoykep+m73V7g/8Av2itQowvJIx6j5/OpRjfUWrax051RPY6LqFy8YACkebO4Z7HjNWC
+       Y0ToW2utJbqPreW5na4O4R78cehY9+fQVKLX05p1ro97awC4kX8VFmJLeEKdoyfOxJPbHbHY
+       VAz0rqKPWNauri9t1ktJgILYgEiNFJ/Op9STmrBBdeaFCo0y3jt444DI3jLE204Y5DD+9UPP
+       hv0NaRtPq99mSLOy22nOfdh75oNC0vT7m1uJGvGiii3AW8UDbQAeDu9zUohLlhouqap1VciF
+       4I1FtbxSthtobBIPuTn7VBJ2vUth1TYk2ULi9iALwyKQYz6Z9CM0Ft060/DWEcDHe2Mucdye
+       9BFW3SekWt3LPb2cUTsd2UGCG9x7VYMzufhzdX/WmoXj+BPpx3I7XZMmXYckc8EVRKaP8I+n
+       XikLQuxOQSTnBzj6UEjonw90XSrq3S3s7dplJcl1EhGDx396lFx1TQ4NVjaC7UNCU2lAO/8A
+       vNWDCLp9X+E/WgWImXSp2LRq7eSRT3B9iP8AFSjd9P1+21TRodVsMS2rLmTbyUGOePXFQSlt
+       cLcW0c1vh4mXcjDswoDLdRm4FvISsgXeTghDzjAPbPyzmgVFxCzuiOC0WN3sM+5oDqySJmNg
+       3rx7UFb60tNUubGJdFdEvhIdryflUFSM49TVgyTQenJemOorf/U4H1Fpp1edAh/hOQTuQ937
+       5JA4q0bnbPBcxxT20wkjKkqYzlT9ayGWu38um2MbLEJ7iRtoUds+/wAgBzQeW/iHqi6j1Pdz
+       eL44HkL9txHtWhVUOVyvHNB2F9zQT8hUAhAdp5FCG0mSAzE4HGDRppvwL1bwOpJbEsFiuYyV
+       X3deR98Zoy2ZtRgmjkSRZocEp51K7se3vUow/rfpFE124mVpfD4mk2MWdCc4A+VWCH1281/U
+       mFnpklzPYRFBEG8uGC4yQfcn9alGgaJo95rRsbi53WaxwrHOm7BjYcHnvz/moJaw07pXSI5I
+       W1K0CQnDhpAWB9R796CudY62msTRW+gadI8KnDXMkLLv9MA8HGOKC3dAXF1dRfh75f41moi4
+       G0AdwcfTj7UFhv7RjqMBV5AJFZHkR8FRjIx6CgpXVNjJ1JqNn07p26CztSJLlpIydyj2J+fG
+       aC+afplrazqLa3SKNIggx3IHYUEsBk4wQc4oK11L1z070/M9rql6wuVA3QopLcjNBDwfFboq
+       eSO2W7kQNxuaEhQfnQLt8TuireVoV1UeXnckTFT9DigHRuv+mbu9ZV1W3Nyc4IRlVl9O47/K
+       gtU+s6baw+JcX1umRkAuM/p3oK/1t0rYdX6cqXKESqN8Ug/Mp9P1qwZ702mo9GdUTWJsmOn3
+       EY8CAORGXJAwScjJ5q0bJDNLb6YklxbKsgA3wwndg9sDtWQN3aw3ZKTwLLEQOGORn6ehoG1v
+       YAw3FikRt7JSuH3Hc/GSc5P0oHn4aO2uGuYyiose044wBQIm6F1dwfh5ARs8R8L2BHGfnQHv
+       9PS4PjxrGLtFKxysm4qD3oCxboIIo7e1jhQHzAYUJ8wP8VYM56j1ktaal1BMrS2sAaK1OQDD
+       6eUepY9yfQVR5zv7hrmaSaRtzyHJY0DcE4BPIPb5UBwOO4oJYzFvzEYHAH9XvQhNZN7AEgDO
+       SDRo/wBA1SXRdVtNQgb/AJUgfBHcZ/8AGaGPVlhPbarZ2t5CEeORBKje2RUrI1zYxTBhMinI
+       wcjvUEcugWkO4AMisMEA8N69u3yoERrOhWNxNYy6hapcxAeJHM+D8u9ASLStLlm/EWdpZyxy
+       +Z3RQ3I5B44oJKTT4blFWSNBEOeBg5+goFYbOK1TKhIxnIbGMH50Cpcyo6AMrIcM2OD68Ggb
+       6Lbbllu5Cd88hYBu6rztWglSNkfm4P70Gaat8Rba96w0vp3R2mhufxyi4kO3YyDOV9+f8VYM
+       w+P0cP8Ax4JVuEKzW8bEr5tuMj0+lUZ7Y2X4288GK+towRlZXYqv9uKCQi6YmbT2u11GzaJW
+       KnYxbBB49KCFnhubdiwL+U8OhP60ElpXUFxY6nDdXQF0qYbZKxwT9vWg3npb42aHcmC11C3u
+       LSQjEkpIdQfr3xUondP6x6e6tv7e101hczRzrNtaFiFC/wAxPYHtUF+lj8bYCTgMG59cUC4O
+       BQQOo2eoXepFTeL/AKYQN1sEwWx6Fu+DQLX2kw3Ok3Vjas9qJ48Exd1PHb9KCE6R0G86Ut7m
+       K71KK4gklM7TyKRIBjtjtjj96AOreudJsrMJbXksk8jBCbVdzRjONxBHP/3QQ9x1jcWGkERW
+       WqXdpMPCt7x4wfFOOWPbA+fAqwZr8TJ9Qbp2ymvEjsrSTEVvawyHz45Lv6E4wPqaoyl8g91O
+       KDlYEc8mgKW5PH7UE80f8PPHl7gDFAZkUjawUIQG8w5ouknAUbl9Dg59KK1X4Z9XXFvo8mlR
+       Xax3KHfBG8Rk3qe6jHOc54+dMZO7jr/qK8vWtba4tYu38TwvDOMc8N60wPLbrW10PS7pnvrn
+       UtbAKobgBUUk9jg8f/lMC2rydMdRSaRqWoLEbx4UefwxkL2BVvvn9Klgv3Tp0lPxFno6wJ4I
+       VmEOMEHsf2xUD2e4ks7n+NGDaCMu8q8lCMcEfPNA6tW/EwrMybEYZUHnI9Cf/FAzu7G4LXTW
+       s38SRNqhs4Bz3z9KCO6x07UNT6altNMvEs7xkC7nPlI9R2/egwbrDT+r+kupLCeK9nu3KBYG
+       Ry+QvdWFWCU6avtA6h1iKDqLRhpmpvkxz2p8JGb3z7k557VRX77TdHteuPBut401CAouyXGc
+       dvmASaC069030brYaay/CwPFwWspRErfUN7UGZX2hWSiY6ZrMc0CvhUlBUk/UcGgiLq2mtG8
+       F54yDwfDfIoGkrRsSZXwOB2zmgsvw06XHVfUcFvI22xQ753Ze4H8v3OBUo9a6XodjpltHbab
+       DHZIhVsQqBuA9DxznFQTQUe5oDYGMnn7UEbpV3JqDyz+BJFbBtsXiDBf/qx6CgDXL42cSRwG
+       M3UzBQrHGFzy32oG2i2kKTSI80lzMow0rqQoyew+3tQO59KtJJRI9rEzgg7igz3z/egZ6paJ
+       dGGwW4eBXy7pGeXUdwT6A5qwebPjJrcOr9TvbWZQ2Onr+Gi2nIJHcj7+vyqigOuRk+vtxQAq
+       Ek4BU0ABj7j96CzzKxYD0GeM0CQG3OMAjkfOgbSZwzE4PJyfWi6caTdzaffW95akrPFIrLg/
+       tRHpGzs9C6t0W31FrO3Y43MrcbH9c/eloZ6v0JpWoKlrHHBbScSFEHYc5Pz71NDXUoJrK1g0
+       dvw9qsspW3nXaN6KMrHnH5ieSfan0U3Rr1uidaRbiwk8BUSS6naQkjc3ZcHaRnn70wbja6lZ
+       X+nw3NvMksM+FXnPJ9DUCeoXj2hSG2t2km7op4U/f/FAvHaNePb3N0jRzRA7VD8Akc9u/FAv
+       cxnawZQ3HYtjJoKfDFAdeub6Vo1dSULIBtTbgYOfU8jNWUVvrm5ih0m1urixhlsI5HJliOwR
+       5/LkkHHJPamjHdQ0HWdemlutN0+YWBYtC0rHDhjwVz3zWgx1n4e9U6QE8XT5Zd//APR5se9X
+       BXbjS9S0zAvbO5tyWKAOhG4/KpQ3ZHXO5JFK98qRUGhfCbph77Uvx91pv463wVjR0LR7s483
+       796WjW7rTrXpHWrSW2YK7lmXTbaIFpCRjjHOOSeeBipaNLtXuIre3R43lnkGXYADZnnmoH6D
+       kbzn6UDGWe9a/hKG3jsW8riQMJS3svp86B6zgMQmDtGT8qDNb6XUpOoPx72tzOkjFYowOduD
+       wDjj70Gg6Wsq2KNeAJKRuKk52fIn5DvVkENrvW+iaSAsl0txI2Asdud5Yk4A4pgzv4l9ST6J
+       ZSXbyyprWpw+FFa5G21gz5icfzH3pgwCSQlh688+tUEwjjngZ7UHAHuWAHpn+1AXj5frQWXO
+       GBZcKSe3c/rQIyAtnI27eBj/ADQIMAuH8vHBB9DQwVpPLjOckEZ/ahi9/Czqj/S9VhtLm6aG
+       1uZFUsT5VOfX5Gpg9GiNJArxsrxsv1BHypYGF7pljeG3kvLZSlqzNGGxhSRjP6GoG1yLUWiW
+       c2nSPA4KJGItyYHYHHarop3wu0jWYNUvzriNBp8ErraRMANxJ/N7nA7ZqDUHgSQLvAbacjI7
+       H3oOuIFuYzGS68jJRyp4+lA0m0yDwGjiTw3bzBwTuDe+TQVbV+mLmW1NtbSok9weZiC2zPLE
+       Z+/FBM6Xo40/TYdL8Jr21G4vJcuCck55HbFWUKQ/h4tR/DTz2o8TK21qmMgKOf8Afpmroklj
+       G1i5BHI57D5VNorut9Lab1LA638W6H8sboNrqQckq3pntV0RWsfD6K7SGC3vTFahQJY2iVmk
+       I9d+Mimie0Hp2DQ7AQacio3JZgqjcT3zxk1KHGldPWtnqMupS5uNTmGGnk5KjGNqf0r8qgmk
+       hVGcquGblm96BDUZZYLGVrdN8+MIvux7UGKTdXdbaRrFvbaxbWN4d58BmwCjHPORycLx2q4L
+       Ne9S9TdN6I13qkWmzSXdwBCGlO4hiMKAB2A9ag0WySQwpLLtMjDcQBhVz6CgoHxF17XbnUYu
+       nulgsUsu4TTvjIUAEhR+x4qwZwtkOi7651PXJobm4tohHbQhdgecjnaPZeOfeqMy1vWLvWNQ
+       lvb+ZpJpWyT2A9gB6Cgjy5AO4A5oAGRgBR39aA7Z8MkFtxPY0AbV9UGfpQWhj5TkBQGxuBoE
+       GG1iQCyHvj1oELgSkK6oRnIBIxzRdJ28Q3+fHiAds96LoH3AF1wCfyijNbF8JviI1rbQ6Pq/
+       iSopxFOx5Uf0n3qUbWQk8II2SRyDPuCDUHMpSIiJQSBwDwKAgTxApnEbyIQ+APyn0+9A5B3A
+       gfmoEWgcb3VlMpGFYjt+negSs7zxH/C3RWO9UElM8SAHG5fl/agNdXcEbJAZ1WadvDQDJO7G
+       f7c0ED1dqWv2enzw6Rb24nZfJd3EwREHqxyMZHzOKBbT7H8PZWTK5uZ9o3Xm1SzEry5PsT7U
+       FF+JnUezSZ9LttRs2km2m5KSFWXDZI491AyBzVwK6J8T7CRtPjee0tbaGAtdNISdoXgLEo5J
+       JxyfSmC6aF1fo2vELZXDJOxwkMybHYe4HtUFiJWJd8rKqjuTQcZV8SNI0dy43BlGVA+ZoBni
+       E0RU7tp77Tg5zQUv4hNrU2tdNWOhylPEnaS5UHGYlAzn5cn74oHGt6l0z07k6nJC123HhKPF
+       lbPptGTj9q0Kx0XMvU+ty6vqQtpWlZo4LOdGDWsak8Aflycgk+v2qC8a1q8OnaXLPOz2kCIW
+       kZ+CqjgYx6n0xTBkmp9Sabp0KdRyI5vJkaGw08MVKIDw8jA557896QY1q2o3eqXr3N7O8skj
+       nlnzgn5e1UMCGV/MOM0BJFOVwfX0oFtgZTjkg8UBtpOeO1B3hg85FBZXVMM+VJAA2+h96BO4
+       IMJ/p7qvuKBKacmOKB5CYYx5UzgDPfHzoGwD5OApJHAHrQEAk8NWdNpx+XdkA0AIGhkGWOQc
+       gg8UGw/DP4kmwhi07WCTZqAiSbstGc4+pFKNvs5o7q2Sa3kWaJxkOp4NZDOHUh/qL2k8LW78
+       eG7kbZv+0+/yoEZp7fUpX/BXpgvYZTCSRtO7vtwe/vQdY6jeyatPp91FEPw8aSNMoYbi2cAD
+       t6Z70DS60KW7luJdV1JniJzBtURG2b0KN7+/vQKSWUWnLLqN3cSLP4ex5Y8jxiPykr23+nzz
+       igpXUPVOu6Vqmmf8UWttb9OXDqkjRnfI3H849uRkDOKC0axLFr+nLB0rrUMM0bqCIGGGX1AH
+       09qsGc9UfBiTV9Vhu9Pu5oPGLNeG6bczN7jHvVEr058Gre3sLeDVrmOdo3LmSFNjEH+XdntQ
+       XO51XQOl5YrCKGWa8SMYS3tzMyLjjJHb9alDqz1S5ktJ7nVdLmSVDiOONfEMiE4Xy+h9xUDm
+       11CaTxEOn3VmpHFxKFCr9s5GPmKAus6jb9M6RJf3c88yxpjcxL7uM5OO3HrQVTSupoOuYdZM
+       TSpptriNFtXKXDA/Pjhs9h2xQOJ7Xpnpa2S91WK2swqjbGw3ysfcnuxrQsGmapYvpwvra1Ft
+       DL52Mi+Gx49sZoMb+LXV0t+jWl3OYLGTO2zjx4h2nyszHsG4/Sgxt5ZJmEsjl3PB3GgSlyXB
+       xkDnNADseB2PsaA8SoXQyFgmQCV70B5R/EIjYmPJxnvigEKcYDfrQF2/X9aCwqC7l2zwfT/F
+       AJJ5747E5oGTqZArKOfccftQHZWwmOD23Z4NAEkTEBmwR7g5AoG7KSSE5HyFAMTyQsMHa/cE
+       cEc5oL58P+v7rppdryPNAXx+GfsQe7Z9D2/Wg3zSdX0fqzT08F433eYwscOpHt9PcVkQPW63
+       2mX9vfWdrbXiRgrIdu2eHIwGD57+nIoKdc/ELVdC0u5afp27SUtta5vJMMzk+UDjzYHtQWDp
+       j4gxXmif/wAitXZkGZzFGW8MehZO+PmM0Fibr/poWQmF6xXA2xmFg59sKRk0C2nQP1KFvdb0
+       vwIUJNtDKcsVP8zD0Jx2oJDTNA0vR5p7qzs44pJOXkUc/SgNfa/pNvbF5L2JgTsCo2WJzjGK
+       A1jcNcxOF/m/I0zbt4+gxgenNA5s7CCxWWYQxpNLgyMiY3nt2oHajcuexAoEL1C1uyFkVHO1
+       ixHb17/KgwT4rdXWep6oul2OpywaTYqVLxDyySDjaM9x6Z+tWDPdB1TW7Wa9sumpGlursqMQ
+       RlpXwd3BA4571RcdN0i41G7h/wBSmNxqdkwn1O6u5/4cAXlYgcnngE/pQNvih8S211obHRyY
+       bWInfJG/Ex9MD2+tBmNzcTXTtJcSSSSHH5jngDAH0oEdxbg5A9wKAzPwO+fegSLfLJoFoR5f
+       UfegXOcHGAT8qAM+XaBzQCCwGDuyPlQWJpV37Q5TIweM7u/f9qA9vNYpFML2KaR8YiaJgAh5
+       5PvQRvnYoqA+MThcDnPai4PKWQlH3K6tggjnPaiEmz4ZznBPfFAVWG0AZDH1z2HzoE7lhv8A
+       JIHI/mGRQI8AEeuOMGgndN6pu7V4RJLKY4WDLhypXj0oNL6d+MMS4ttetDdQgDNwAPEwPRh2
+       NZGkabrvTXVZiexvba5aI+ILWZQG3Y4IDdvtQScvTdjK8chtFjkQ+VlYgqPXGKA17daV07ap
+       JrV9CsZbELTgbu3YY78UEHf9evJ4K9P6JqGoiR1XxjCUjAJ5OT3NBM9YdSWPTenwy3t7bWbS
+       uBunUthfUhRyT+1BA2vXnS/jGdeo9LnIHljeLwSCe5zgmgejrOz1S3kGhazoaXYGAJ5Sw3e3
+       8uaCsJfX02rPD1XfXtvcCXdBJGjLbOO+EK88Y7nIoLB1H1Bb6WkN1ddSQ29io/5MZEjzt7DG
+       Tjj2FXNGUfEH4wRaxCtnp2kwGGM7llvBvIOO4XOAe/fNMwZbqusalfLBHfzSvFH+SIgKo+ig
+       YqiwWfWV30rpp03p6exJnUPJexQnxuR+Ulu2PkKCrT6jcSiTxZnbxCWcFidxPJJ96BBDlQ35
+       fbFAbahBHJIGO+KAIwg5YnB455oDkKcbW7UAOowSMjOORQcCVXPp+9AqDkHcDmgMNpHPcUBT
+       uzQWCJXcFvKR2Y0CMiqjnz4UcH/6oG8gKluwxyAfSjQ6ylEJBJfv37/OiYQMjbjkZXOSP/FE
+       ELAEkNn1waBCXdjORnPoc80CZeTb5wQe2BQAm4y7jlhjvQcWO3cW4GB7ZpgGC5a3uUeNyGQ5
+       GCR+45pgt+l/EzqLTgxj1O5Zc4CSOXCj70wOE+Jd/Pq0V7qVvb3bISAWUBhnuc+/2pgvkHx0
+       soLaNIdKkEiqR5yDg47cYpgresfELSNc1n8VrFtAw2ZBiiywwcgeb14x2xTBYNA13ozUo/GO
+       qWVizDc1nf6crIh/7wOf1rOURvVupdE6ncpFeakiSWsZdbjSLfw1Zs+VVyMHA75xWsFDHU0i
+       dTJdf65rT28YKpPvHjKp9Bk49qYK/rGpXF/qU9zPdyzyyMSJJAAx9ifnVlwNZ7vxYEh8GAEH
+       O8DDH5H5U0IPK8jHxSzFQAMnOKgJkFwPT6UBlAII5z3zQCjnA5OKBXeuAT9KABMA208DtxQH
+       RhzgUC3KjJx9z2oAP6/L3NAHC8Hg/XNAcNtGe4oEy5yeaCdLk7yx2qQCAO1AmXZSSexHbPNA
+       hJuLJkgjvzRonuAJy2cd8UCbyOi453Dj7GiYTZyVPPl74oYLJIyq68EH3Gf3oYTDEjLbiP1F
+       EDHOUOdgOfXIBFAm77j5Mnng/WgLI5UE8Eg9iO9AnuJbuQx5wOBQFRxuIbOc54PrQK28ws76
+       F722EyI4d4HJUOPb35yKBm8oeQsi4BPbPb71RyYIHmPGRg00GRsbsHIPY0Bg52AEEseBUCQJ
+       yOPXtQDtcdvvjtQCQ/GBwKAuXU4PrQBvZE5IGeO1AffjBJ57UBvEO045HagFWAAzktQKIzBe
+       Mg0C5kz5mOG/WgMrDJJ7jmgEnIyOccYoA3cEMRj05oC7/wDeBQTduzEoNxwcZGaAJOWfPNAl
+       ISVOT60aIQfkj+amgJ3bnnigKeFGPQUCf/x0Smw4V8exogX/AOY3+/SgKxKxeU459KAgJOck
+       ntQJkkcgkGgAAFFz/XQEmJaY7jnk96BM9yPQelAf/wCX7UBv/jagGP8AKB6ZoDf00HMfO/0o
+       AH5TQA/5moECSMDPFAvGASMjPP8AigVX+b60BW7/AHoHEJJD55oDd4snv70CsSjCcDmgVAAb
+       gYoGYJ3nk9qBUAYHAoP/2Q==
+      </office:binary-data>
+     </draw:image>
+    </draw:frame></text:p>
+  </office:text>
+ </office:body>
+</office:document>
diff --git a/test/odt/fodt/preformattedText.fodt b/test/odt/fodt/preformattedText.fodt
new file mode 100644
--- /dev/null
+++ b/test/odt/fodt/preformattedText.fodt
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:creation-date>2026-02-15T01:39:34.300934683</meta:creation-date><dc:date>2026-02-15T01:40:03.737721904</dc:date><meta:editing-duration>PT29S</meta:editing-duration><meta:editing-cycles>1</meta:editing-cycles><meta:generator>LibreOffice/24.8.7.2$Linux_AARCH64 LibreOffice_project/480$Build-2</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="3" meta:word-count="6" meta:character-count="40" meta:non-whitespace-character-count="33"/></office:meta>
+ <office:font-face-decls>
+  <style:font-face style:name="Liberation Mono" svg:font-family="&apos;Liberation Mono&apos;" style:font-family-generic="modern" style:font-pitch="fixed"/>
+  <style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable"/>
+  <style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
+  <style:font-face style:name="Noto Sans" svg:font-family="&apos;Noto Sans&apos;" style:font-family-generic="swiss"/>
+  <style:font-face style:name="Noto Sans SC Thin" svg:font-family="&apos;Noto Sans SC Thin&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="Noto Sans1" svg:font-family="&apos;Noto Sans&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="graphic">
+   <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+   <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0in" style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="GB" style:letter-kerning="true" style:font-name-asian="Noto Sans SC Thin" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+  </style:default-style>
+  <style:default-style style:family="paragraph">
+   <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="GB" style:letter-kerning="true" style:font-name-asian="Noto Sans SC Thin" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
+  </style:default-style>
+  <style:default-style style:family="table">
+   <style:table-properties table:border-model="collapsing"/>
+  </style:default-style>
+  <style:default-style style:family="table-row">
+   <style:table-row-properties fo:keep-together="auto"/>
+  </style:default-style>
+  <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+  <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+   <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" fo:keep-with-next="always"/>
+   <style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="Noto Sans SC Thin" style:font-family-asian="&apos;Noto Sans SC Thin&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Noto Sans1" style:font-family-complex="&apos;Noto Sans&apos;" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+  </style:style>
+  <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+   <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0972in" style:contextual-spacing="false" fo:line-height="115%"/>
+  </style:style>
+  <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
+   <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Noto Sans" style:font-family-complex="&apos;Noto Sans&apos;" style:font-family-generic-complex="swiss"/>
+  </style:style>
+  <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="Noto Sans" style:font-family-complex="&apos;Noto Sans&apos;" style:font-family-generic-complex="swiss" style:font-size-complex="12pt" style:font-style-complex="italic"/>
+  </style:style>
+  <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
+   <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+   <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Noto Sans" style:font-family-complex="&apos;Noto Sans&apos;" style:font-family-generic-complex="swiss"/>
+  </style:style>
+  <style:style style:name="Preformatted_20_Text" style:display-name="Preformatted Text" style:family="paragraph" style:parent-style-name="Standard" style:class="html">
+   <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0in" style:contextual-spacing="false"/>
+   <style:text-properties style:font-name="Liberation Mono" fo:font-family="&apos;Liberation Mono&apos;" style:font-family-generic="modern" style:font-pitch="fixed" fo:font-size="10pt" style:font-name-asian="Liberation Mono" style:font-family-asian="&apos;Liberation Mono&apos;" style:font-family-generic-asian="modern" style:font-pitch-asian="fixed" style:font-size-asian="10pt" style:font-name-complex="Liberation Mono" style:font-family-complex="&apos;Liberation Mono&apos;" style:font-family-generic-complex="modern" style:font-pitch-complex="fixed" style:font-size-complex="10pt"/>
+  </style:style>
+  <text:outline-style style:name="Outline">
+   <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+  </text:outline-style>
+  <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+  <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+  <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
+  <loext:theme loext:name="Office">
+   <loext:theme-colors loext:name="LibreOffice">
+    <loext:color loext:name="dark1" loext:color="#000000"/>
+    <loext:color loext:name="light1" loext:color="#ffffff"/>
+    <loext:color loext:name="dark2" loext:color="#000000"/>
+    <loext:color loext:name="light2" loext:color="#ffffff"/>
+    <loext:color loext:name="accent1" loext:color="#18a303"/>
+    <loext:color loext:name="accent2" loext:color="#0369a3"/>
+    <loext:color loext:name="accent3" loext:color="#a33e03"/>
+    <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+    <loext:color loext:name="accent5" loext:color="#c99c00"/>
+    <loext:color loext:name="accent6" loext:color="#c9211e"/>
+    <loext:color loext:name="hyperlink" loext:color="#0000ee"/>
+    <loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
+   </loext:theme-colors>
+  </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in">
+    <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+   </style:page-layout-properties>
+   <style:header-style/>
+   <style:footer-style/>
+  </style:page-layout>
+  <style:style style:name="dp1" style:family="drawing-page">
+   <style:drawing-page-properties draw:background-size="full"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:body>
+  <office:text>
+   <text:sequence-decls>
+    <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+   </text:sequence-decls>
+   <text:p text:style-name="Preformatted_20_Text">void main() {</text:p>
+   <text:p text:style-name="Preformatted_20_Text"><text:s text:c="4"/>printf(&quot;Hello world&quot;);</text:p>
+   <text:p text:style-name="Preformatted_20_Text">}</text:p>
+  </office:text>
+ </office:body>
+</office:document>
diff --git a/test/odt/fodt/textMixedStyles.fodt b/test/odt/fodt/textMixedStyles.fodt
new file mode 100644
--- /dev/null
+++ b/test/odt/fodt/textMixedStyles.fodt
@@ -0,0 +1,224 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:meta><meta:creation-date>2016-10-20T14:33:00.091715893</meta:creation-date><dc:date>2016-12-13T11:16:41.168160323</dc:date><meta:editing-duration>PT1H59M24S</meta:editing-duration><meta:editing-cycles>10</meta:editing-cycles><meta:generator>LibreOffice/24.8.7.2$Linux_AARCH64 LibreOffice_project/480$Build-2</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="3" meta:word-count="27" meta:character-count="139" meta:non-whitespace-character-count="114"/></office:meta>
+ <office:font-face-decls>
+  <style:font-face style:name="Droid Sans Fallback" svg:font-family="&apos;Droid Sans Fallback&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="FreeSans" svg:font-family="FreeSans" style:font-family-generic="swiss"/>
+  <style:font-face style:name="FreeSans1" svg:font-family="FreeSans" style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable"/>
+  <style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="graphic">
+   <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+   <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0in" style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Droid Sans Fallback" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="FreeSans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/>
+  </style:default-style>
+  <style:default-style style:family="paragraph">
+   <style:paragraph-properties fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Droid Sans Fallback" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="FreeSans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
+  </style:default-style>
+  <style:default-style style:family="table">
+   <style:table-properties table:border-model="collapsing"/>
+  </style:default-style>
+  <style:default-style style:family="table-row">
+   <style:table-row-properties fo:keep-together="auto"/>
+  </style:default-style>
+  <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
+  <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+   <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" fo:keep-with-next="always"/>
+   <style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="Droid Sans Fallback" style:font-family-asian="&apos;Droid Sans Fallback&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="FreeSans1" style:font-family-complex="FreeSans" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+  </style:style>
+  <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+   <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0972in" style:contextual-spacing="false" fo:line-height="120%"/>
+  </style:style>
+  <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
+   <style:text-properties style:font-size-asian="12pt" style:font-name-complex="FreeSans" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss"/>
+  </style:style>
+  <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="FreeSans" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss" style:font-size-complex="12pt" style:font-style-complex="italic"/>
+  </style:style>
+  <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
+   <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+   <style:text-properties style:font-size-asian="12pt" style:font-name-complex="FreeSans" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss"/>
+  </style:style>
+  <style:style style:name="Quotations" style:family="paragraph" style:parent-style-name="Standard" style:class="html">
+   <style:paragraph-properties fo:margin-left="0.3937in" fo:margin-right="0.3937in" fo:margin-top="0in" fo:margin-bottom="0.1965in" style:contextual-spacing="false" fo:text-indent="0in" style:auto-text-indent="false"/>
+  </style:style>
+  <style:style style:name="Title" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
+   <style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
+   <style:text-properties fo:font-size="28pt" fo:font-weight="bold" style:font-size-asian="28pt" style:font-weight-asian="bold" style:font-size-complex="28pt" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Subtitle" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
+   <style:paragraph-properties fo:margin-top="0.0417in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" fo:text-align="center" style:justify-single-word="false"/>
+   <style:text-properties fo:font-size="18pt" style:font-size-asian="18pt" style:font-size-complex="18pt"/>
+  </style:style>
+  <style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text">
+   <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="130%" fo:font-weight="bold" style:font-size-asian="130%" style:font-weight-asian="bold" style:font-size-complex="130%" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text">
+   <style:paragraph-properties fo:margin-top="0.139in" fo:margin-bottom="0.0835in" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="115%" fo:font-weight="bold" style:font-size-asian="115%" style:font-weight-asian="bold" style:font-size-complex="115%" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="3" style:class="text">
+   <style:paragraph-properties fo:margin-top="0.0972in" fo:margin-bottom="0.0835in" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="101%" fo:font-weight="bold" style:font-size-asian="101%" style:font-weight-asian="bold" style:font-size-complex="101%" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="Internet_20_link" style:display-name="Internet link" style:family="text">
+   <style:text-properties fo:color="#000080" loext:opacity="100%" fo:language="zxx" fo:country="none" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/>
+  </style:style>
+  <style:style style:name="Visited_20_Internet_20_Link" style:display-name="Visited Internet Link" style:family="text">
+   <style:text-properties fo:color="#800000" loext:opacity="100%" fo:language="zxx" fo:country="none" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" style:language-asian="zxx" style:country-asian="none" style:language-complex="zxx" style:country-complex="none"/>
+  </style:style>
+  <style:style style:name="Graphics" style:family="graphic">
+   <style:graphic-properties text:anchor-type="paragraph" svg:x="0in" svg:y="0in" style:wrap="dynamic" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph" draw:fill="none"/>
+  </style:style>
+  <text:outline-style style:name="Outline">
+   <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.3in" fo:text-indent="-0.3in" fo:margin-left="0.3in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.4in" fo:text-indent="-0.4in" fo:margin-left="0.4in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.5in" fo:text-indent="-0.5in" fo:margin-left="0.5in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.6in" fo:text-indent="-0.6in" fo:margin-left="0.6in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7in" fo:text-indent="-0.7in" fo:margin-left="0.7in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.8in" fo:text-indent="-0.8in" fo:margin-left="0.8in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.9in" fo:text-indent="-0.9in" fo:margin-left="0.9in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1in" fo:text-indent="-1in" fo:margin-left="1in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.1in" fo:text-indent="-1.1in" fo:margin-left="1.1in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
+    <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.2in" fo:text-indent="-1.2in" fo:margin-left="1.2in"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+  </text:outline-style>
+  <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
+  <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
+  <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
+  <loext:theme loext:name="Office">
+   <loext:theme-colors loext:name="LibreOffice">
+    <loext:color loext:name="dark1" loext:color="#000000"/>
+    <loext:color loext:name="light1" loext:color="#ffffff"/>
+    <loext:color loext:name="dark2" loext:color="#000000"/>
+    <loext:color loext:name="light2" loext:color="#ffffff"/>
+    <loext:color loext:name="accent1" loext:color="#18a303"/>
+    <loext:color loext:name="accent2" loext:color="#0369a3"/>
+    <loext:color loext:name="accent3" loext:color="#a33e03"/>
+    <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+    <loext:color loext:name="accent5" loext:color="#c99c00"/>
+    <loext:color loext:name="accent6" loext:color="#c9211e"/>
+    <loext:color loext:name="hyperlink" loext:color="#0000ee"/>
+    <loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
+   </loext:theme-colors>
+  </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
+   <style:paragraph-properties fo:line-height="115%" fo:break-before="auto" fo:break-after="auto" style:writing-mode="lr-tb"/>
+   <style:text-properties officeooo:rsid="0014be9a" officeooo:paragraph-rsid="0016b660"/>
+  </style:style>
+  <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Standard">
+   <style:paragraph-properties fo:line-height="115%" style:writing-mode="lr-tb"/>
+   <style:text-properties officeooo:rsid="0014be9a" officeooo:paragraph-rsid="0016b660"/>
+  </style:style>
+  <style:style style:name="P3" style:family="paragraph" style:parent-style-name="Standard">
+   <style:paragraph-properties fo:line-height="115%" fo:break-before="auto" fo:break-after="auto" style:writing-mode="lr-tb"/>
+   <style:text-properties officeooo:paragraph-rsid="0016b660"/>
+  </style:style>
+  <style:style style:name="P4" style:family="paragraph" style:parent-style-name="Standard">
+   <style:paragraph-properties fo:line-height="115%" style:writing-mode="lr-tb"/>
+   <style:text-properties style:text-underline-style="none" officeooo:rsid="0017c72f" officeooo:paragraph-rsid="0017c72f"/>
+  </style:style>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"/>
+  </style:style>
+  <style:style style:name="T2" style:family="text">
+   <style:text-properties fo:font-style="normal" fo:font-weight="bold" style:font-style-asian="normal" style:font-weight-asian="bold" style:font-style-complex="normal" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="T3" style:family="text">
+   <style:text-properties fo:font-style="normal" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" fo:font-weight="normal" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-style-complex="normal" style:font-weight-complex="normal"/>
+  </style:style>
+  <style:style style:name="T4" style:family="text">
+   <style:text-properties fo:font-style="normal" style:text-underline-style="none" fo:font-weight="normal" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-style-complex="normal" style:font-weight-complex="normal"/>
+  </style:style>
+  <style:style style:name="T5" style:family="text">
+   <style:text-properties fo:font-style="italic" style:text-underline-style="none" fo:font-weight="bold" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-style-complex="italic" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="T6" style:family="text">
+   <style:text-properties fo:font-style="normal" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" fo:font-weight="bold" style:font-style-asian="normal" style:font-weight-asian="bold" style:font-style-complex="normal" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="T7" style:family="text">
+   <style:text-properties style:text-line-through-style="solid" style:text-line-through-type="single" fo:font-style="normal" style:text-underline-style="none" fo:font-weight="bold" style:font-style-asian="normal" style:font-weight-asian="bold" style:font-style-complex="normal" style:font-weight-complex="bold"/>
+  </style:style>
+  <style:style style:name="T8" style:family="text">
+   <style:text-properties style:text-position="0% 100%"/>
+  </style:style>
+  <style:style style:name="T9" style:family="text">
+   <style:text-properties style:text-position="super 58%" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" fo:font-weight="normal" style:font-weight-asian="normal" style:font-weight-complex="normal"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="8.5in" fo:page-height="11in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in">
+    <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
+   </style:page-layout-properties>
+   <style:header-style/>
+   <style:footer-style/>
+  </style:page-layout>
+  <style:style style:name="dp1" style:family="drawing-page">
+   <style:drawing-page-properties draw:background-size="full"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:body>
+  <office:text>
+   <text:sequence-decls>
+    <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+   </text:sequence-decls>
+   <text:p text:style-name="P1">This is a simple text</text:p>
+   <text:p text:style-name="P2"/>
+   <text:p text:style-name="P2">that is both <text:span text:style-name="T1">italic </text:span><text:span text:style-name="T2">bold </text:span><text:span text:style-name="T3">underlined </text:span><text:span text:style-name="T4">and the </text:span><text:span text:style-name="T5">first two</text:span><text:span text:style-name="T4"> and the </text:span><text:span text:style-name="T6">last two </text:span><text:span text:style-name="T4"><text:s/>and </text:span><text:span text:style-name="T7">bold and line through</text:span></text:p>
+   <text:p text:style-name="P3"/>
+   <text:p text:style-name="P4">And wit<text:span text:style-name="T8">h </text:span><text:span text:style-name="T9">superscripts</text:span></text:p>
+  </office:text>
+ </office:body>
+</office:document>
diff --git a/test/odt/native/referenceToChapter.native b/test/odt/native/referenceToChapter.native
--- a/test/odt/native/referenceToChapter.native
+++ b/test/odt/native/referenceToChapter.native
@@ -1,1 +1,1 @@
-[Header 1 ("a-chapter",[],[]) [Span ("anchor",[],[]) [],Str "A",Space,Str "chapter"],Para [Str "Some",Space,Str "text."],Header 1 ("another-chapter",[],[]) [Str "Another",Space,Str "chapter"],Para [Str "A",Space,Str "reference",Space,Str "to",Space,Str "."],Para [Str "A",Space,Str "reference",Space,Str "to",Space,Link ("",[],[]) [Str "A",Space,Str "chapter"] ("#anchor",""),Str "."]]
+[Header 1 ("a-chapter",[],[]) [Span ("anchor",[],[]) [],Str "A",Space,Str "chapter"],Para [Str "Some",Space,Str "text."],Header 1 ("another-chapter",[],[]) [Str "Another",Space,Str "chapter"],Para [Str "A",Space,Str "reference",Space,Str "to",Space,Str "Another",Space,Str "chapter."],Para [Str "A",Space,Str "reference",Space,Str "to",Space,Link ("",[],[]) [Str "A",Space,Str "chapter"] ("#anchor",""),Str "."]]
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/rtf/unicode.native b/test/rtf/unicode.native
--- a/test/rtf/unicode.native
+++ b/test/rtf/unicode.native
@@ -1,3 +1,7 @@
 Pandoc
   Meta { unMeta = fromList [] }
-  [ Para [ Str "\8220hi\8221\8216hi\8217\61623\945\228" ] ]
+  [ Para
+      [ Str
+          "\8220hi\8221\8216hi\8217\61623\945\228\119867unpaired"
+      ]
+  ]
diff --git a/test/rtf/unicode.rtf b/test/rtf/unicode.rtf
--- a/test/rtf/unicode.rtf
+++ b/test/rtf/unicode.rtf
@@ -5,5 +5,7 @@
 \u-3913?
 \u945a
 {\uc2\u228\'E8a}
+\uc1\u-10187 ?\u-9157 ?
+\u-10187 ?unpaired
 \par
 }
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
@@ -45,6 +46,7 @@
 import qualified Tests.Writers.Ms
 import qualified Tests.Writers.Muse
 import qualified Tests.Writers.Native
+import qualified Tests.Writers.ODT
 import qualified Tests.Writers.Org
 import qualified Tests.Writers.Plain
 import qualified Tests.Writers.Powerpoint
@@ -65,6 +67,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
@@ -78,6 +81,7 @@
           , testGroup "Plain" Tests.Writers.Plain.tests
           , testGroup "AsciiDoc" Tests.Writers.AsciiDoc.tests
           , testGroup "Docx" Tests.Writers.Docx.tests
+          , testGroup "ODT" Tests.Writers.ODT.tests
           , testGroup "RST" Tests.Writers.RST.tests
           , testGroup "TEI" Tests.Writers.TEI.tests
           , testGroup "markua" Tests.Writers.Markua.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,54 +139,182 @@
                       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" ] ]
                   ]
               ]
           ]
           (TableFoot ( "" , [] , [] ) [])
       ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "1" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "A"
+                      , Space
+                      , Str "longer"
+                      , Space
+                      , Str "descriptive"
+                      , Space
+                      , Str "label"
+                      , Space
+                      , Str "that"
+                      , Space
+                      , Str "wraps"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "2" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Another"
+                      , Space
+                      , Str "longer"
+                      , Space
+                      , Str "label"
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidth 0.25 )
+      , ( AlignDefault , ColWidth 0.25 )
+      , ( AlignDefault , ColWidth 0.5 )
+      ]
+      (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" ] ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "d" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "e" ] ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
   , Div
       ( "" , [ "columns-flow" ] , [ ( "count" , "2" ) ] )
       [ Para
@@ -204,11 +332,11 @@
                       , Space
                       , Str "Undergrads"
                       ]
-                  , SoftBreak
                   ]
                   ( "https://github.com/johanvx/typst-undergradmath"
                   , ""
                   )
+              , SoftBreak
               ]
           ]
       , Para
@@ -294,7 +422,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Span
                               ( "" , [ "box" ] , [] )
                               [ Str "2023-05-22"
@@ -308,7 +436,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Str "This"
                           , Space
                           , Str "is"
@@ -349,7 +477,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Span
                               ( "" , [ "box" ] , [] ) [ Str "\128166" ]
                           ]
@@ -359,7 +487,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Str "Get"
                           , Space
                           , Str "this"
@@ -404,7 +532,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Span
                               ( "" , [ "box" ] , [] )
                               [ Str "No"
@@ -420,7 +548,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Str "Don\8217t"
                           , Space
                           , Str "know"
@@ -558,7 +686,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "x^{2}"
@@ -572,7 +700,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sqrt{2}"
@@ -595,7 +723,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "x_{i,j}"
@@ -609,7 +737,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\frac{2}{3}"
@@ -683,7 +811,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\alpha"
@@ -697,7 +825,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\xi"
@@ -720,7 +848,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\beta"
@@ -734,7 +862,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\959"
@@ -751,7 +879,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\gamma"
@@ -771,7 +899,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\pi"
@@ -794,7 +922,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\delta"
@@ -814,7 +942,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varpi"
@@ -831,7 +959,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\epsilon"
@@ -845,7 +973,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\rho"
@@ -862,7 +990,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varepsilon"
@@ -876,7 +1004,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varrho"
@@ -893,7 +1021,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\zeta"
@@ -907,7 +1035,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sigma"
@@ -930,7 +1058,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\eta"
@@ -944,7 +1072,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varsigma"
@@ -966,7 +1094,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\theta"
@@ -986,7 +1114,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\tau"
@@ -1003,7 +1131,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\vartheta"
@@ -1017,7 +1145,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\upsilon"
@@ -1040,7 +1168,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\iota"
@@ -1054,7 +1182,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\phi"
@@ -1077,7 +1205,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\kappa"
@@ -1091,7 +1219,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varphi"
@@ -1108,7 +1236,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\lambda"
@@ -1128,7 +1256,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\chi"
@@ -1145,7 +1273,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mu"
@@ -1159,7 +1287,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\psi"
@@ -1182,7 +1310,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nu"
@@ -1196,7 +1324,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\omega"
@@ -1246,7 +1374,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cup"
@@ -1260,7 +1388,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mathbb{R}"
@@ -1277,7 +1405,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\forall"
@@ -1294,7 +1422,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cap"
@@ -1308,7 +1436,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mathbb{Z}"
@@ -1325,7 +1453,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\exists"
@@ -1342,7 +1470,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\subset"
@@ -1356,7 +1484,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mathbb{Q}"
@@ -1373,7 +1501,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\neg"
@@ -1390,7 +1518,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\subseteq"
@@ -1404,7 +1532,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mathbb{N}"
@@ -1421,7 +1549,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\vee"
@@ -1438,7 +1566,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\supset"
@@ -1452,7 +1580,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mathbb{C}"
@@ -1469,7 +1597,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\land"
@@ -1486,7 +1614,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\supseteq"
@@ -1500,7 +1628,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varnothing"
@@ -1514,7 +1642,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\vdash"
@@ -1531,7 +1659,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\in"
@@ -1545,7 +1673,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varnothing"
@@ -1559,7 +1687,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\models"
@@ -1576,7 +1704,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\notin"
@@ -1590,7 +1718,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\1488"
@@ -1604,7 +1732,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\smallsetminus"
@@ -1814,7 +1942,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "f'"
@@ -1831,7 +1959,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\dot{a}"
@@ -1845,10 +1973,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 +1990,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "f''"
@@ -1877,7 +2005,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ddot{a}"
@@ -1891,7 +2019,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\overline{a}"
@@ -1908,7 +2036,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\Sigma^{\\ast}"
@@ -1922,7 +2050,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\hat{a}"
@@ -1936,7 +2064,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -2181,7 +2309,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sin"
@@ -2195,7 +2323,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sinh"
@@ -2209,7 +2337,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\arcsin"
@@ -2226,7 +2354,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cos"
@@ -2240,7 +2368,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cosh"
@@ -2254,7 +2382,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\arccos"
@@ -2271,7 +2399,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\tan"
@@ -2285,7 +2413,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\tanh"
@@ -2299,7 +2427,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\arctan"
@@ -2316,7 +2444,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sec"
@@ -2330,7 +2458,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\coth"
@@ -2344,7 +2472,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\min"
@@ -2361,7 +2489,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\csc"
@@ -2375,7 +2503,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\det"
@@ -2389,7 +2517,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\max"
@@ -2406,7 +2534,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cot"
@@ -2420,7 +2548,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\dim"
@@ -2434,7 +2562,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\inf"
@@ -2451,7 +2579,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\exp"
@@ -2465,7 +2593,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ker"
@@ -2479,7 +2607,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sup"
@@ -2496,7 +2624,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\log"
@@ -2510,7 +2638,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\deg"
@@ -2524,7 +2652,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\liminf"
@@ -2541,7 +2669,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ln"
@@ -2555,7 +2683,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\arg"
@@ -2569,7 +2697,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\limsup"
@@ -2586,7 +2714,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\lg"
@@ -2600,7 +2728,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\gcd"
@@ -2614,7 +2742,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\lim"
@@ -2651,7 +2779,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "<"
@@ -2668,7 +2796,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\angle"
@@ -2682,7 +2810,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cdot"
@@ -2699,7 +2827,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\leq"
@@ -2716,7 +2844,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\measuredangle"
@@ -2730,7 +2858,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\pm"
@@ -2747,7 +2875,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath ">"
@@ -2764,7 +2892,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ell"
@@ -2778,7 +2906,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mp"
@@ -2795,7 +2923,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\geq"
@@ -2812,7 +2940,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\parallel"
@@ -2826,7 +2954,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\times"
@@ -2843,7 +2971,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\neq"
@@ -2860,7 +2988,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "45{^\\circ}"
@@ -2874,7 +3002,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\div"
@@ -2891,7 +3019,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ll"
@@ -2908,7 +3036,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cong"
@@ -2922,7 +3050,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ast"
@@ -2942,7 +3070,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\gg"
@@ -2959,7 +3087,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ncong"
@@ -2974,7 +3102,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mid"
@@ -2991,7 +3119,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\approx"
@@ -3005,7 +3133,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sim"
@@ -3019,7 +3147,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nmid"
@@ -3036,7 +3164,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\asymp"
@@ -3055,7 +3183,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\simeq"
@@ -3069,7 +3197,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "n!"
@@ -3086,7 +3214,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\equiv"
@@ -3100,7 +3228,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nsim"
@@ -3114,7 +3242,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\partial"
@@ -3131,7 +3259,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\prec"
@@ -3145,7 +3273,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\oplus"
@@ -3159,7 +3287,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nabla"
@@ -3176,7 +3304,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\preceq"
@@ -3190,7 +3318,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ominus"
@@ -3204,7 +3332,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\295"
@@ -3221,7 +3349,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\succ"
@@ -3235,7 +3363,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\odot"
@@ -3249,7 +3377,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\circ"
@@ -3268,7 +3396,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\succeq"
@@ -3282,7 +3410,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\otimes"
@@ -3296,7 +3424,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\star"
@@ -3313,7 +3441,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\propto"
@@ -3327,7 +3455,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\oslash"
@@ -3346,7 +3474,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sqrt{}"
@@ -3363,7 +3491,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\doteq"
@@ -3382,7 +3510,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\upharpoonright"
@@ -3396,7 +3524,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\10003"
@@ -3483,7 +3611,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\rightarrow"
@@ -3500,7 +3628,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mapsto"
@@ -3520,7 +3648,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nrightarrow"
@@ -3534,7 +3662,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\longmapsto"
@@ -3553,7 +3681,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\longrightarrow"
@@ -3567,7 +3695,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\leftarrow"
@@ -3587,7 +3715,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\Rightarrow"
@@ -3605,7 +3733,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -3627,7 +3755,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nRightarrow"
@@ -3643,7 +3771,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\downarrow"
@@ -3660,7 +3788,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\Longrightarrow"
@@ -3676,7 +3804,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\uparrow"
@@ -3693,7 +3821,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\rightsquigarrow"
@@ -3708,7 +3836,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\updownarrow"
@@ -3837,7 +3965,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\int"
@@ -3851,7 +3979,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\iiint"
@@ -3866,7 +3994,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\bigcup"
@@ -3883,7 +4011,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\iint"
@@ -3898,7 +4026,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\oint"
@@ -3913,7 +4041,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\bigcap"
@@ -3949,7 +4077,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "()"
@@ -3963,7 +4091,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\langle\\rangle"
@@ -3979,7 +4107,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -3997,7 +4125,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\lbrack\\rbrack"
@@ -4011,7 +4139,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -4027,7 +4155,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -4046,7 +4174,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -4061,7 +4189,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -5345,5 +5473,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
@@ -24,5 +24,92 @@
   ..nums.map(n => str(fib(n))),
 ))
 
+#table(
+  columns: (auto, 1fr, auto, 1fr),
+  [1], [A longer descriptive label that wraps], [2], [Another longer label],
+)
+
+#table(
+  columns: (1fr, 1fr, 2fr),
+  [a], [b], [c],
+)
+
+#table(
+  columns: (auto, auto),
+  [d], [e],
+)
+
 #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.opendocument b/test/writer.opendocument
--- a/test/writer.opendocument
+++ b/test/writer.opendocument
@@ -12,12 +12,6 @@
     <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Quotations">
       <style:paragraph-properties fo:margin-left="0.5in" fo:margin-right="0in" fo:text-indent="0in" style:auto-text-indent="false" />
     </style:style>
-    <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Quotations">
-      <style:paragraph-properties fo:margin-left="0.5in" fo:margin-right="0in" fo:text-indent="0in" style:auto-text-indent="false" />
-    </style:style>
-    <style:style style:name="P3" style:family="paragraph" style:parent-style-name="Quotations">
-      <style:paragraph-properties fo:margin-left="0.5in" fo:margin-right="0in" fo:text-indent="0in" style:auto-text-indent="false" />
-    </style:style>
     <text:list-style style:name="Pandoc_5f_Numbering_5f_1">
       <text:list-level-style-number text:level="1" text:style-name="Numbering_20_Symbols" style:num-format="1" style:num-prefix="(" style:num-suffix=")">
         <style:list-level-properties text:space-before="0.0000in" text:min-label-width="0.1965in" text:min-label-distance="0.1in" />
@@ -275,7 +269,7 @@
 </text:list>
 <text:p text:style-name="Quotations">Nested block quotes:</text:p>
 <text:p text:style-name="P1">nested</text:p>
-<text:p text:style-name="P2">nested</text:p>
+<text:p text:style-name="P1">nested</text:p>
 <text:p text:style-name="First_20_paragraph">This should not be a block quote: 2
 &gt; 1.</text:p>
 <text:p text:style-name="Text_20_body">And a following paragraph.</text:p>
@@ -620,7 +614,7 @@
 crisp, pleasant to taste</text:p>
 <text:p text:style-name="Definition_20_Term"><text:span text:style-name="Emphasis">orange</text:span></text:p>
 <text:p text:style-name="Definition_20_Definition">orange
-fruit</text:p><text:p text:style-name="Preformatted_20_Text">{ orange code block }</text:p><text:p text:style-name="P3">orange
+fruit</text:p><text:p text:style-name="Preformatted_20_Text">{ orange code block }</text:p><text:p text:style-name="P1">orange
 block quote</text:p>
 <text:p text:style-name="First_20_paragraph">Multiple definitions,
 tight:</text:p>
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/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
@@ -42,12 +42,13 @@
   ) where
 
 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
-import Data.Maybe (mapMaybe)
 import Text.Pandoc.XML.Light.Types
 import Text.Pandoc.XML.Light.Proc
 import Text.Pandoc.XML.Light.Output
@@ -58,56 +59,139 @@
 parseXMLElement = parseXMLElementWithEntities mempty
 
 -- Drop in replacement for parseXMLDoc in xml-light.
+-- Like 'parseXMLContentsWithEntities', but requires the document to
+-- consist of a single root element.
 parseXMLElementWithEntities :: M.Map T.Text T.Text
                             -> TL.Text -> Either T.Text Element
 parseXMLElementWithEntities entityMap t =
-  elementToElement .  Conduit.documentRoot <$>
-    either (Left . T.pack . E.displayException) Right
-    (Conduit.parseText Conduit.def{ Conduit.psRetainNamespaces = True
-                                  , Conduit.psDecodeEntities = decodeEnts } t)
+  parseXMLContentsWithEntities entityMap t >>= documentElement
  where
-   decodeEnts ref = case M.lookup ref entityMap of
-                      Nothing -> XML.ContentEntity ref
-                      Just t' -> XML.ContentText t'
+  -- 'parseXMLContentsWithEntities' has already dropped whitespace
+  -- around a lone root element, so anything else left here is either
+  -- a missing root or content outside of it.
+  documentElement [Elem e] = Right e
+  documentElement cs
+    | null [e | Elem e <- cs] = Left "XML document has no root element"
+    | otherwise = Left "unexpected content after the root XML element"
 
 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 }
 
-elementToElement :: Conduit.Element -> Element
-elementToElement (Conduit.Element name attribMap nodes) =
-  Element (nameToQname name) attrs (mapMaybe nodeToContent nodes) Nothing
+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
-  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
+  -- 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')
 
-nodeToContent :: Conduit.Node -> Maybe Content
-nodeToContent (Conduit.NodeElement el) =
-  Just (Elem (elementToElement el))
-nodeToContent (Conduit.NodeContent t) =
-  Just (Text (CData CDataText t Nothing))
-nodeToContent _ = Nothing
+  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 :: XML.Name -> QName
+nameToQName (XML.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
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)
