pandoc 3.11 → 3.12
raw patch · 233 files changed
+13025/−7835 lines, 233 filesdep +conduitdep +text-builderdep −prettydep −pretty-showdep ~Diffdep ~asciidocdep ~citeprocbinary-addedPVP ok
version bump matches the API change (PVP)
Dependencies added: conduit, text-builder
Dependencies removed: pretty, pretty-show
Dependency ranges changed: Diff, asciidoc, citeproc, commonmark, commonmark-extensions, commonmark-pandoc, crypton, directory, djot, doclayout, doctemplates, skylighting, skylighting-core, texmath, text, typst, unicode-data, zip-archive, zlib
API changes (from Hackage documentation)
Files
- AUTHORS.md +7/−0
- MANUAL.txt +49/−7
- benchmark/benchmark-pandoc.hs +19/−0
- changelog.md +738/−0
- data/templates/common.latex +0/−3
- data/templates/default.revealjs +6/−6
- data/templates/styles.html +36/−13
- pandoc.cabal +36/−34
- src/Text/Pandoc/App/Input.hs +4/−2
- src/Text/Pandoc/Chunks.hs +7/−17
- src/Text/Pandoc/Citeproc.hs +8/−8
- src/Text/Pandoc/Citeproc/BibTeX.hs +14/−17
- src/Text/Pandoc/Citeproc/Locator.hs +3/−3
- src/Text/Pandoc/Citeproc/MetaValue.hs +3/−3
- src/Text/Pandoc/Citeproc/Name.hs +11/−11
- src/Text/Pandoc/Class/IO.hs +6/−99
- src/Text/Pandoc/Class/IO/HTTP.hs +115/−0
- src/Text/Pandoc/Class/PandocMonad.hs +97/−22
- src/Text/Pandoc/Class/PandocPure.hs +24/−15
- src/Text/Pandoc/Data.hs +14/−3
- src/Text/Pandoc/ImageSize.hs +314/−83
- src/Text/Pandoc/MediaBag.hs +37/−16
- src/Text/Pandoc/PDF.hs +2/−2
- src/Text/Pandoc/Parsing.hs +0/−1
- src/Text/Pandoc/Parsing/Capabilities.hs +4/−1
- src/Text/Pandoc/Parsing/Future.hs +2/−5
- src/Text/Pandoc/Parsing/General.hs +47/−21
- src/Text/Pandoc/Parsing/GridTable.hs +1/−7
- src/Text/Pandoc/Parsing/Lists.hs +25/−4
- src/Text/Pandoc/Parsing/Math.hs +5/−3
- src/Text/Pandoc/Readers/AsciiDoc.hs +89/−75
- src/Text/Pandoc/Readers/CommonMark.hs +9/−9
- src/Text/Pandoc/Readers/Creole.hs +5/−5
- src/Text/Pandoc/Readers/Djot.hs +8/−4
- src/Text/Pandoc/Readers/Docx.hs +20/−22
- src/Text/Pandoc/Readers/Docx/Parse.hs +8/−6
- src/Text/Pandoc/Readers/DokuWiki.hs +7/−5
- src/Text/Pandoc/Readers/HTML.hs +97/−43
- src/Text/Pandoc/Readers/HTML/Parsing.hs +39/−26
- src/Text/Pandoc/Readers/HTML/Table.hs +15/−6
- src/Text/Pandoc/Readers/HTML/Types.hs +4/−2
- src/Text/Pandoc/Readers/Jira.hs +2/−2
- src/Text/Pandoc/Readers/LaTeX.hs +182/−42
- src/Text/Pandoc/Readers/LaTeX/Citation.hs +4/−10
- src/Text/Pandoc/Readers/LaTeX/Inline.hs +2/−16
- src/Text/Pandoc/Readers/LaTeX/Macro.hs +266/−2
- src/Text/Pandoc/Readers/LaTeX/Parsing.hs +794/−89
- src/Text/Pandoc/Readers/LaTeX/Table.hs +6/−10
- src/Text/Pandoc/Readers/Man.hs +3/−2
- src/Text/Pandoc/Readers/Markdown.hs +66/−32
- src/Text/Pandoc/Readers/Mdoc.hs +11/−11
- src/Text/Pandoc/Readers/Mdoc/Lex.hs +1/−1
- src/Text/Pandoc/Readers/MediaWiki.hs +14/−12
- src/Text/Pandoc/Readers/Muse.hs +30/−9
- src/Text/Pandoc/Readers/ODT.hs +1/−1
- src/Text/Pandoc/Readers/ODT/Arrows/State.hs +0/−153
- src/Text/Pandoc/Readers/ODT/Arrows/Utils.hs +0/−241
- src/Text/Pandoc/Readers/ODT/Base.hs +0/−26
- src/Text/Pandoc/Readers/ODT/ContentReader.hs +352/−472
- src/Text/Pandoc/Readers/ODT/Generic/Fallible.hs +0/−71
- src/Text/Pandoc/Readers/ODT/Generic/Utils.hs +2/−62
- src/Text/Pandoc/Readers/ODT/Generic/XMLConverter.hs +289/−586
- src/Text/Pandoc/Readers/ODT/StyleReader.hs +107/−142
- src/Text/Pandoc/Readers/Org/BlockStarts.hs +4/−3
- src/Text/Pandoc/Readers/Org/Blocks.hs +27/−20
- src/Text/Pandoc/Readers/Org/ExportSettings.hs +19/−3
- src/Text/Pandoc/Readers/Org/Inlines.hs +23/−17
- src/Text/Pandoc/Readers/Org/Meta.hs +10/−6
- src/Text/Pandoc/Readers/Org/ParserState.hs +14/−4
- src/Text/Pandoc/Readers/Org/Parsing.hs +6/−2
- src/Text/Pandoc/Readers/Org/Shared.hs +2/−1
- src/Text/Pandoc/Readers/Pod.hs +12/−12
- src/Text/Pandoc/Readers/RST.hs +121/−53
- src/Text/Pandoc/Readers/RTF.hs +4/−4
- src/Text/Pandoc/Readers/Roff.hs +4/−4
- src/Text/Pandoc/Readers/Roff/Escape.hs +2/−2
- src/Text/Pandoc/Readers/TWiki.hs +8/−5
- src/Text/Pandoc/Readers/Textile.hs +4/−3
- src/Text/Pandoc/Readers/TikiWiki.hs +2/−2
- src/Text/Pandoc/Readers/Txt2Tags.hs +10/−8
- src/Text/Pandoc/Readers/Typst.hs +229/−150
- src/Text/Pandoc/Readers/Typst/Parsing.hs +2/−2
- src/Text/Pandoc/Readers/Vimwiki.hs +14/−9
- src/Text/Pandoc/Readers/XML.hs +55/−37
- src/Text/Pandoc/SelfContained.hs +60/−37
- src/Text/Pandoc/Shared.hs +72/−16
- src/Text/Pandoc/Sources.hs +78/−0
- src/Text/Pandoc/TeX.hs +45/−1
- src/Text/Pandoc/Translations.hs +6/−1
- src/Text/Pandoc/Translations/Types.hs +11/−4
- src/Text/Pandoc/URI.hs +1/−1
- src/Text/Pandoc/UTF8.hs +13/−5
- src/Text/Pandoc/Writers/ANSI.hs +6/−1
- src/Text/Pandoc/Writers/ChunkedHTML.hs +4/−3
- src/Text/Pandoc/Writers/Djot.hs +3/−3
- src/Text/Pandoc/Writers/DocBook.hs +1/−1
- src/Text/Pandoc/Writers/Docx.hs +18/−6
- src/Text/Pandoc/Writers/Docx/OpenXML.hs +168/−55
- src/Text/Pandoc/Writers/Docx/Table.hs +3/−1
- src/Text/Pandoc/Writers/Docx/Types.hs +7/−2
- src/Text/Pandoc/Writers/EPUB.hs +23/−26
- src/Text/Pandoc/Writers/FB2.hs +2/−2
- src/Text/Pandoc/Writers/HTML.hs +97/−40
- src/Text/Pandoc/Writers/Haddock.hs +1/−1
- src/Text/Pandoc/Writers/JATS.hs +4/−4
- src/Text/Pandoc/Writers/Jira.hs +4/−3
- src/Text/Pandoc/Writers/LaTeX.hs +46/−50
- src/Text/Pandoc/Writers/LaTeX/Table.hs +3/−11
- src/Text/Pandoc/Writers/LaTeX/Types.hs +0/−2
- src/Text/Pandoc/Writers/LaTeX/Util.hs +2/−2
- src/Text/Pandoc/Writers/Markdown.hs +2/−2
- src/Text/Pandoc/Writers/Markdown/Inline.hs +4/−4
- src/Text/Pandoc/Writers/MediaWiki.hs +1/−1
- src/Text/Pandoc/Writers/Ms.hs +3/−3
- src/Text/Pandoc/Writers/Native.hs +284/−9
- src/Text/Pandoc/Writers/ODT.hs +3/−3
- src/Text/Pandoc/Writers/OOXML.hs +2/−2
- src/Text/Pandoc/Writers/OPML.hs +2/−1
- src/Text/Pandoc/Writers/Org.hs +90/−31
- src/Text/Pandoc/Writers/Powerpoint/Output.hs +37/−14
- src/Text/Pandoc/Writers/Powerpoint/Presentation.hs +11/−7
- src/Text/Pandoc/Writers/RST.hs +11/−4
- src/Text/Pandoc/Writers/Shared.hs +60/−44
- src/Text/Pandoc/Writers/TEI.hs +1/−1
- src/Text/Pandoc/Writers/Typst.hs +17/−15
- src/Text/Pandoc/Writers/XML.hs +161/−66
- src/Text/Pandoc/XMLFormat.hs +89/−1
- test/Tests/ImageSize.hs +335/−0
- test/Tests/MediaBag.hs +35/−1
- test/Tests/Readers/Docx.hs +4/−0
- test/Tests/Readers/LaTeX.hs +45/−0
- test/Tests/Readers/Org/Directive.hs +12/−0
- test/Tests/Readers/Org/Inline.hs +8/−0
- test/Tests/Readers/Org/Inline/Note.hs +7/−0
- test/Tests/Shared.hs +8/−0
- test/Tests/Writers/Docx.hs +21/−0
- test/ansi-test.ansi +7/−0
- test/ansi-test.txt +6/−0
- test/asciidoc-reader.native +3831/−3849
- test/command/10390.md +2/−2
- test/command/10490.md +16/−16
- test/command/10643.md +11/−1
- test/command/10965.md +0/−4
- test/command/11583.md +0/−1
- test/command/1166.md +6/−6
- test/command/11794.md +0/−1
- test/command/11849.md +42/−0
- test/command/11858.md +11/−0
- test/command/11869.md +8/−0
- test/command/11888.md +46/−0
- test/command/11888/section1/lab.md +11/−0
- test/command/11888/section2/lab.md +8/−0
- test/command/11888/section3/lab.md +7/−0
- test/command/11897.md +67/−0
- test/command/2378.md +2/−1
- test/command/2606.md +2/−2
- test/command/2623.md +7/−0
- test/command/2623.odt binary
- test/command/2649.md +15/−15
- test/command/2994.md +2/−2
- test/command/3958.md +2/−2
- test/command/5367.md +9/−3
- test/command/6549.md +3/−3
- test/command/6959.md +5/−4
- test/command/6992.md +5/−5
- test/command/7214.md +1/−1
- test/command/7436.md +2/−2
- test/command/8110.md +9/−9
- test/command/8257.md +1/−1
- test/command/8659.md +5/−5
- test/command/8770-block.md +1/−1
- test/command/8770-document.md +1/−1
- test/command/8770-section.md +1/−1
- test/command/8948.md +1/−1
- test/command/9387.md +0/−1
- test/command/9467.md +1/−1
- test/command/9635.md +1/−1
- test/command/9716.md +1/−1
- test/command/9807.md +1/−3
- test/command/biblatex-edtf-date.md +1/−0
- test/command/completion.md +3/−3
- test/command/latex-opt-arg-state.md +38/−0
- test/command/latex-tokenize-hash-positions.md +13/−0
- test/command/latex-url-comment-positions.md +12/−0
- test/command/latex3-commands.md +473/−0
- test/command/latex3-usrguide.md +453/−0
- test/command/pandoc-citeproc-392.md +2/−2
- test/command/pandoc-citeproc-chicago-fullnote-bibliography.md +3/−2
- test/command/typst-highlight.md +98/−0
- test/command/typst-property-output.md +2/−0
- test/djot-reader.native +11/−12
- test/docx/comments.native +152/−4
- test/docx/csl_bibliography.native +50/−0
- test/docx/golden/block_quotes.docx binary
- test/docx/golden/comments.docx binary
- test/docx/golden/csl_bibliography.docx binary
- test/docx/golden/custom_style_preserve.docx binary
- test/docx/golden/headers.docx binary
- test/docx/golden/image.docx binary
- test/docx/golden/links.docx binary
- test/docx/golden/lists.docx binary
- test/docx/golden/lists_continuing.docx binary
- test/docx/golden/lists_div_bullets.docx binary
- test/docx/golden/lists_multiple_initial.docx binary
- test/docx/golden/lists_restarting.docx binary
- test/docx/golden/nested_anchors_in_header.docx binary
- test/docx/golden/notes.docx binary
- test/docx/golden/table_with_list_cell.docx binary
- test/docx/golden/tables-default-widths.docx binary
- test/docx/golden/tables.docx binary
- test/docx/golden/task_list.docx binary
- test/docx/golden/track_changes_scrubbed_metadata.docx binary
- test/docx/link_tooltips.docx binary
- test/docx/link_tooltips.native +112/−0
- test/docx/table_with_list_cell.native +2/−2
- test/docx/track_changes_scrubbed_metadata.native +26/−7
- test/jats-reader.native +3/−1
- test/jats-reader.xml +2/−2
- test/markdown-reader-more.native +1/−1
- test/mediawiki-reader.native +39/−39
- test/odt/native/simpleTableWithCaption.native +1/−1
- test/rst-reader.native +1/−1
- test/test-pandoc.hs +2/−0
- test/typst-reader.native +408/−197
- test/typst-reader.typ +72/−0
- test/vimwiki-reader.native +1/−4
- test/writer.html4 +20/−12
- test/writer.html5 +20/−12
- test/writer.org +46/−46
- test/writer.tei +13/−13
- test/writer.typst +0/−2
- xml-light/Text/Pandoc/XML/Light.hs +122/−28
- xml-light/Text/Pandoc/XML/Light/Output.hs +96/−63
@@ -25,6 +25,7 @@ - Amy de Buitléir - Anabra - Anders Waldenborg+- Andonome - Andreas Deininger - Andreas Lööw - Andreas Scherer@@ -87,6 +88,7 @@ - Christoffer Sawicki - Christophe Dervieux - Christopher Kenny+- Clar Fon - Clare Macrae - Clint Adams - Conal Elliott@@ -151,6 +153,7 @@ - Fyodor Sheremetyev - Gabor Pali - Gabriel Lewertowski+- Gaurav Vijay Jadhav - Gavin Beatty - George Stagg - Georgi Lyubenov@@ -366,6 +369,7 @@ - Recai Oktaş - Repetitive - Reuben Thomas+- Robert Szarka - Robertas - Rowan Rodrik van der Molen - Roland Hieber@@ -378,6 +382,7 @@ - Salim B - Sam S. Almahri - Sam May+- Samuel Huang - Samuel Tardieu - Saumel Lemmenmeier - Santiago Zarate@@ -467,6 +472,7 @@ - Yoan Blanc - You Jiangbin - Yuchen Pei+- Yusuf Efe - Zihang Chen - 3w36zj6 - arcnmx@@ -549,4 +555,5 @@ - willj-dev - wuffi - wzy+- zenor0 - λx.x
@@ -1,7 +1,7 @@ --- title: Pandoc User's Guide author: John MacFarlane-date: 2026-08-28+date: 2026-09-26 --- # Synopsis@@ -2937,17 +2937,24 @@ to e.g. `20px`, but it also accepts `pt` (12pt = 16px in most browsers). -`fontcolor`+`fontcolor`, `fontcolordark` : sets the CSS `color` property on the `html` element. -`linkcolor`+`linkcolor`, `linkcolordark` : sets the CSS `color` property on all links. +`quotebordercolor`, `quotebordercolordark`+: sets the CSS `border-color` property for the left-border of+: `blockquote` elements.++`quotecolor`, `quotecolordark`+: sets the CSS `color` property for `blockquote` elements.+ `monofont`-: sets the CSS `font-family` property on `code` elements.+: sets the CSS `font-family` property on `code` and `pre` elements. -`monobackgroundcolor`-: sets the CSS `background-color` property on `code` elements+`monobackgroundcolor`, `monobackgroundcolordark`+: sets the CSS `background-color` property on `code` and `pre` elements and adds extra padding. `linestretch`@@ -2957,7 +2964,7 @@ `maxwidth` : sets the CSS `max-width` property (default is 36em). -`backgroundcolor`+`backgroundcolor`, `backgroundcolordark` : sets the CSS `background-color` property on the `html` element. `margin-left`, `margin-right`, `margin-top`, `margin-bottom`@@ -2984,6 +2991,41 @@ --- [CSS]: https://developer.mozilla.org/en-US/docs/Learn/CSS++#### Note on `color-scheme`++By default, the browser's chosen `color-scheme` (either `light` or `dark`)+will affect the color of various unstyled elements like input fields, which are+generally only applicable if your document is specifically targeting HTML.++By default, Pandoc emits `color-scheme: light dark` to allow the system to+choose between a default light-mode or dark-mode scheme, but it alters this+if variables specifically override colors, under the assumption that the user+wishes for that scheme to always be used.++Currently, this is only per a basic heuristic that checks whether the following+variables are provided:++* `fontcolor`+* `fontcolordark`+* `backgroundcolor`+* `backgroundcolordark`++And if only one "mode" is provided (light or dark), `color-scheme` is set so+that this chosen mode is used always, regardless of the system's preference for+light or dark mode.++Note that there are a few caveats to this, namely that:++* If the browser does not support `light-dark` (older than ~2024), then it will+ always choose light mode, regardless of this override.+* When printing, light mode is always chosen, and color variables are ignored.+* Syntax highlighting is currently unaffected by color scheme.++In general, if you wish to override the theming for a single scheme, you should+choose to override either the normal `*color` variables (light mode) or the+`*colordark` variables (dark mode) to ensure that the styles do not conflict+with other styles added by new versions of Pandoc or the browser itself. ### Variables for HTML math
@@ -18,6 +18,7 @@ -} import Text.Pandoc import Text.Pandoc.MIME+import Text.Pandoc.Shared (stringify, stringifyInlines) import Control.DeepSeq (force) import Control.Monad.Except (throwError) import qualified Text.Pandoc.UTF8 as UTF8@@ -89,6 +90,19 @@ writerFun def{ writerExtensions = wexts} d) doc +-- | A large inline sequence exercising both the common constructors+-- and the ones 'stringify' special-cases ('Quoted', 'Note', 'Cite').+bigInlines :: [Inline]+bigInlines = concat $ replicate 1000+ [ Str "Lorem", Space+ , Emph [Str "ipsum", Space, Strong [Str "dolor"]], Space+ , Quoted DoubleQuote [Str "sit", Space, Str "amet"], SoftBreak+ , Link nullAttr [Str "consectetur"] ("https://example.com", "title")+ , Space, Code nullAttr "adipiscing", Space+ , Note [Para [Str "footnote", Space, Emph [Str "text"]]]+ , Cite [] [Str "elit"], LineBreak+ ]+ main :: IO () main = do inp <- UTF8.toText <$> B.readFile "test/testsuite.txt"@@ -102,4 +116,9 @@ , bgroup "readers" $ mapMaybe (readerBench doc . fst) (sortOn fst readers :: [(T.Text, Reader PandocPure)])+ , env (pure $ force bigInlines) $ \ils ->+ bgroup "stringify"+ [ bench "stringify" $ nf stringify ils+ , bench "stringifyInlines" $ nf stringifyInlines ils+ ] ]
@@ -1,5 +1,738 @@ # Revision history for pandoc +## pandoc 3.12 (2026-09-27)++ * Markdown reader:++ + Make alert keywords case-insensitive (#11836, Hendrik Erz).+ In addition, the class `alert` is now added to the produced+ Divs.+ + Fix stream position handling in `base64DataURI`.+ + Cheaply reject `bareURL` before trying uri/emailAddress.+ + Reset sourcepos in `parseWithString'` when parsing a block+ quote or list item. Otherwise it can happen that by the+ time `parseWithString'` is called, the position has already+ been set to the next file on the command line. Fixes an+ odd bug with `rebase_relative_paths` (#11888).+ + `rebase_relative_paths`: recognize URLs with unknown schemes+ (#11858).+ + Use `takeWhile1P` in the hot inline parsers `str`, `code`,+ `enclosure`, and `mmdShortSubscript`.+ + Reset sourcepos in `parseWithString'` when parsing a block quote+ or list item (#11888). This ensures that `rebase_relative_paths`+ will see the right source file.++ * Typst reader:++ + Map `form: "prose"` citations to AuthorInText (#11846,+ Samuel Huang).+ + Make the handler maps monomorphic by wrapping the handlers+ in newtypes with polymorphic fields (BlockHandler, InlineHandler).+ + Store document labels in a Set instead of a list.+ + In `pInline`, only perform the label-target check for+ `ref` elements, not for every inline element.+ + Handle `highlight` as a mark span (#11879, Samuel Huang).+ + Collapse citations around soft break (#11897).+ + Handle block content in inline element bodies (#11881,+ Samuel Huang).+ + Handle `#par` (explicit paragraph element).++ * LaTeX reader:++ + Support LaTeX3 (xparse) document commands (#7540).+ Support the features described in the LaTeX usrguide.+ + Fix `\qed` to produce U+00A0 (nbsp) instead of BEL.+ + Fix `unescapeURL` handling of escaped backslash.+ + Require that `\newif` names begin with "if".+ + Fix position off-by-one after `##` in tokenizer.+ + Fix doubled source positions in `retokenizeComment`.+ + Make raw token capture O(1) per token.+ + Make `untokenize` linear instead of quadratic.+ + Fail `macroDef` fast on non-macro-defining commands.+ + Peek at next token directly in `peekTok` instead of going+ through `satisfyTok`.+ + Skip `doMacros` state update for non-macro tokens.+ + Build command dispatch maps (`inlineCommands`, etc.) once per parse.+ + Keep ASCII quotes when ligatures are disabled.+ + Don't discard state changes made in optional arguments.+ + Keep nested conditionals balanced in `\iftrue` etc.++ * Docx reader:++ + Read ScreenTips as link titles (#11869, Robert Szarka).+ + Use `comment-id` instead of `id` in AST for comments.++ * ODT reader:++ + Rewrite in monadic style. Net -994 lines. No changes to test output.+ + Handle `textProperties` on paragraph styles (#2623).+ Previously these just got ignored, not applied to the+ paragraph's text.++ * HTML reader:++ + Don't require a closing tag for checkbox inputs.+ `<input>` is a void element.+ + Allow omitted `</tr>` in tables. The `</tr>` closing+ tag is optional in HTML.+ + Don't drop cells when there are too few `<col>` elements.+ + Let the first of duplicate attributes win. HTML specifies that+ the first occurrence wins.+ + Respect `raw_html` for inline `<style>` elements.+ + Use a Map for the note table.+ + Report the noteref position for unresolved notes. The+ ReferenceNotFound warning was logged after the+ whole document had been parsed, so it always pointed at the+ end of the input. Record the position of the first reference+ to each note and use it in the warning.+ + Find list style anywhere in the class attribute.+ Previously `<ol class="fancy lower-roman">` got DefaultStyle,+ because the whole class attribute was compared against the+ known style names. Check each class individually. As a side+ effect, an unrecognized class no longer prevents falling back+ to the style attribute.+ + Limit iframe nesting depth.+ + `htmlTag`: Don't copy the remaining input on each invocation. A+ space was appended to the remaining input to guarantee a+ TagPosition token after the parsed tag; since the input is a+ strict Text, this copied the entire remaining input every+ time htmlTag was called (e.g. for every inline HTML tag in a+ markdown document), giving quadratic behavior in tag-dense+ documents. Instead, handle the case where the tag is the+ final token by computing the end-of-input position directly.+ + Pass the tag name to `pSpanLike`. The inline dispatcher already+ knows which span-like element it is looking at, so there is+ no need for pSpanLike to try a parser.+ + Fix `pre/code` attribute precedence for first-wins dedup.+ + Implement `pSatisfy` as a single parsec primitive.+ + Add a fast path to `pTagText`: when the text contains no+ character that could parse as anything but Str, Space, or+ SoftBreak under the enabled extensions (and we are not in a+ pre element), return B.text directly.+ + Reorder `pTagContents` to try `pStr` and `pSpace` before the+ math, smart punctuation, and raw TeX parsers. This is safe+ because pStr cannot consume the special characters that+ start those parsers, and it avoids most guard checks on the+ slow path. This makes the html reader benchmark about 33%+ faster and halves its allocation.++ * Muse reader:++ + Try `str` earlier in inline parser. This makes the reader 2x+ faster and reduces heap allocation by 60%.+ + Check raw line before parsing table rows. Table row parsers+ are speculatively tried at every paragraph line boundary via+ the terminator continuation. This change gives another 2x+ speed improvement.++ * Org reader:++ + Fix hard parse failure on bare backslash.+ + Recognize `.pdf` as an image format (#11859). This matches+ the behavior of Emacs, which will render `[[file:foo.pdf]]`+ as an image.+ + Make `#+OPTIONS: ^:nil` disable all sub-/superscript parsing.+ + Allow `-` and `_` in inline footnote labels. Org footnote labels+ may contain word-constituent characters, hyphens and underscores.+ + Fix swapped arguments in exportSettings parser.+ + Don't lowercase meta keyword twice.+ + Avoid quadratic complexity when parsing table rows. Parsing+ a 32000-row table drops from 7.8s to 0.9s (and scales+ linearly instead of quadratically); maximum residency also improves.+ + Use a Set for anchor ids. Reading a document with 64000+ anchors and as many internal links drops from 23.5s to 5.2s+ and now scales linearly.++ * RST reader:++ + Use a predicate to test for for special characters.+ + Avoid double-parsing lines preceding a non-underline.+ + Fail fast when no link can start at this position.+ + Use `lookupGE` to find the next anonymous key. Parsing a+ document with 16000 anonymous links drops from 5.2s to 2.0s.+ + Replace association list with Map for resolving note references.+ Parsing a document with 16000 named notes drops from 5.8s+ to 3.5s, and scaling is now linear.+ + Fix a bug that led to an empty class for code blocks with+ no specified language.++ * CommonMark reader:++ + Avoid nested walks in `tex_math_gfm` handling.+ + Avoid rebuilding the token list with `map id` in+ `sourceToToks` in the common case where the source starts+ at line 1.++ * AsciiDoc reader:++ + Resolve footnotes, stem, and icons during conversion.+ Previously the reader made three separate passes+ over the parsed AsciiDoc AST (for footnotes, stem math types,+ and icons) before converting to the pandoc AST. Instead,+ resolve all three during the toPandoc conversion, which+ already traverses everything once, threading footnote state+ and document attributes through a StateT layer. Table+ headers are now converted before body rows so that footnote+ resolution follows document order. This makes the reader+ about twice as fast on typical documents.++ * Djot reader:++ + Fix a bug that led to an empty class for code blocks with+ no specified language.++ * Vimwiki reader:++ + Split class attribute and put it in the proper slot in+ pandoc's Attr.++ * Typst writer:++ + Omit blank line at end of block (#11844). This is just a+ cosmetic change; it is not semantically significant.+ + Emit label for bare table if present (#11849). Previously a label+ was only emitted if the table was figurized (and not+ `.typst:no-figure`).++ * RST writer:++ + Apply `nowrap` to just footnote label, not body.+ This bug surfaced after `nowrap` was fixed in doclayout.++ * Docx writer:++ + Fix sizing for images (#11838). Setting size to a percent+ now scales image to percent of page width. Previously a+ percent width or height would just provide a maximum bound+ rather than scaling.+ + Include default style even if paragraph has non-style props+ (#11867). Previously a paragraph that was, e.g. center-aligned+ would be missing its default Body Text style. Also ensure that+ any block sequence (list items, table cells, block quote,+ div), First Paragraph is set for the first paragraph in the item.+ + Use `_` to start all bookmark names (#11845). This ensures that+ they are "hidden" and will not be read by screen readers.+ + Honor CSL hanging-indent and spacing hints (#11871, Samuel Huang).+ + Add a fast path to `withDirection`.+ + Avoid double `withDirection` for Space and SoftBreak.+ + Map link/image titles to ScreenTips.+ + Write link titles as ScreenTips (#11869, Robert Szarka).+ + Properly signal error if reference docx can't be parsed.+ Previously this led to an incomprehensible error at a later+ phase.+ + Make `convertSpace` linear instead of quadratic. On an ad+ hoc benchmark (4 paragraphs of 80K words each), conversion+ time drops from 3.2s to 1.1s, and time no longer depends on+ paragraph length (16 x 20K words previously took 1.8s, now+ also 1.2s).+ + Cache token-type styles instead of rebuilding per Code.+ On an ad hoc benchmark with 100K inline code spans,+ conversion time drops from 2.4s to 1.5s.+ + Add FirstParagraph class after display math (#11900).+ This way the continuation text can be styled flush-left+ in a style where normal paragraphs are indented.+ + Treat a "mixed" widths table as all-default (#11899). Previously if+ some columns were ColWidthDefault and others ColWidth 0.x,+ we would get columns with a specified width of 0 for the default+ ones. Instead, treat all columns as default in this case.+ + Use `comment-id` instead of `id` in AST for comments.+ Note that the Docx writer will still interpret an `id`+ attribute for legacy compatibility, so if you use markdown+ files that specify `id`, they should still work.++ * TEI writer:++ + Use `rend`, not `rendition` attribute, on milestone+ (#11842, Yusuf Efe) `rendition` takes pointers to rendition+ descriptions, while `rend` is the free-text attribute,+ which is what a plain "line" value needs.++ * LaTeX writer:++ + Avoid nested walk in table cell line-break handling.+ + Don't use `footnotehyper` for notes in longtable.+ Instead, generate them manually as we do for floating tables.+ This removes our dependency on footnotehyper, and resolves a+ compatibility problem with `endfloat` (#11857).+ + Remove unused `stInternalLinks` state field. The+ field has not been read since the writer switched to always+ adding hypertargets, but we were still doing a full-document+ query to populate it.+ + Avoid needless conversions in `sectionHeader`.+ The note-free and link-free variants of the heading text+ were rendered for every heading, even though the former is+ only needed when the heading contains a note, image, or+ identified span, and the latter only for unnumbered, listed+ headings.+ + Fuse preprocessing passes in `inlineListToLaTeX`.+ The strut-insertion and quote-kerning fixups were separate+ list traversals, allocating an intermediate list each, and+ this function is called at every level of inline nesting.+ Combine them into a single pass.+ + Avoid String round-trip for highlighted inline code.+ + Don't unpack code string to choose `lstinline` delimiter.+ + Only compute PDF trailer ID when `SOURCE_DATE_EPOCH` is set.+ + Use `showHex` instead of (slower) `printf` in `toLabel`.++ * RST writer:++ + Don't re-transform stored labels and alt text.+ Reference labels, image substitution labels, and alt text are+ stored after the document-wide walk in inlineListToRST has+ already applied transformInlines (flattening, backslash-space+ insertion, etc.). Rendering them with inlineListToRST applied+ these non-idempotent transformations a second time, inserting+ duplicate `\ ` markers. With `--reference-links` this could+ make the inline reference and its definition render+ differently, producing a broken RST reference.++ * Native writer:++ + Render directly instead of using pretty-show.+ Previously writeNative used pretty-show's `ppDoc`, which shows+ the document, tokenizes and re-parses the result into a+ generic Value, and lays that out via Text.PrettyPrint.HughesPJ.+ The layout step dominated the cost of the writer (and of+ any pipeline producing native output). We now build a+ width-cached layout tree directly from the AST and render+ it with a small renderer that reproduces HughesPJ's layout+ algorithm exactly (including the ribbon computation with+ ribbonsPerLine = 1.2 and the treatment of glued closing+ delimiters), so the output is byte-for-byte identical to+ before. Verified against the old binary on all golden+ .native files and the markdown test corpus at many column+ widths, in both standalone and plain modes. The writer is+ about 4x faster. Also remove the pretty and pretty-show+ dependencies.++ * XML writer:++ + Respect `--standalone`. When standalone is selected,+ we get a full Pandoc element with xml header and metadata. When+ not, we get a fragment -- just the blocks.+ + Use the pretty-printer instead of manual newlines.+ Render the document with `ppcElement`, using a+ configuration that treats elements with inline content as+ inline tags, so that no significant whitespace is added+ inside them. Consecutive text nodes are merged, SoftBreak+ is written as a literal newline, and whitespace runs that+ would not survive a roundtrip (e.g. `" \n"` or `"\n\n"`)+ are encoded as Space and SoftBreak elements.+ + Encode attribute names that are not valid XML names.+ Pandoc attribute names may contain characters that are not+ allowed in XML attribute names, such as colons+ (`typst:property`). Use the common convention of encoding+ such characters as `_xHHHH_`, where `HHHH` is the hexadecimal+ code of the character: the writer encodes attribute names+ (`foo:bar` becomes `foo_x003A_bar`) and the reader decodes+ them.+ + Don't drop attributes with empty values. The writer dropped+ any attribute with an empty value, so user key-value+ attributes like `("k","")` disappeared and did not round+ trip.+ + Improve performance: the XML writer is now on par with the+ JSON writer.++ * EPUB writer:++ + Render TOC item titles in the host monad. Previously each TOC+ item title in the nav entry was rendered with a separate+ `runPure (writeHtmlStringForEPUB ...)`. Since every one of+ these invocations starts with a fresh CommonState, the+ translations YAML file was re-read and re-parsed for every+ TOC item, which accounted for a significant part of the+ EPUB writer's run time on documents with many sections.+ + Speed up MathML/SVG detection for the manifest.++ * Powerpoint writer:++ + Add archive entries in a single pass.+ + Cache the parsed slide master in WriterEnv.+ + Skip speaker-notes walk when there are no notes.++ * ANSI writer:++ + Fix missing bar on blockquote's first line (#11804,+ Gaurav Vijay Jadhav D.).++ * HTML writer:++ + Fix typo in `intrinsicEventsHTML4`. The list had+ `onmouseout` twice and was missing `onmousemove`.+ + Don't emit `<p></p>` for paragraphs with no rendered content.+ + Improve email obfuscation. Preserve formatting (e.g. emphasis)+ in the link text of obfuscated mailto links. Preserve link+ attributes in reference- and JavaScript-obfuscated links.+ Avoid double-escaping the already-rendered link text in the+ fallback branch for unparseable mailto URLs.+ + Respect `--id-prefix` in EPUB3 footnote section id.+ + Respect incremental/nonincremental classes inside columns.+ + Make KaTeX CSS URL handling consistent with the JS URL.+ + Use truncate consistently for table width percentages.+ Previously the table width could exceed the sum of column widths.+ + Avoid walking section contents when not producing slides.+ + Hoist attribute set unions to top level.+ + Make `strToHtml` more efficient. Replace the+ `T.groupBy`-based implementation, which allocated a list of+ Text fragments and round-tripped through String, with a+ simple `T.break` scanner.++ * Org writer:++ + Don't render table body rows twice. Table rows+ were converted once to compute column widths and then again+ to produce the output. Since blockListToOrg is stateful, any+ footnote in a table cell was registered twice, yielding+ duplicate footnote definitions and skewed numbering; it also+ doubled the rendering work. Reuse the first conversion.+ + Use `#+begin_export html` for raw HTML blocks. `#+begin_html`+ was removed in Org 9.0 (2016).+ + Don't treat bare punctuation as a list marker.+ The check that keeps ordered list markers from ending up at+ the beginning of a line matched `Str "."` and `Str ")"`,+ since `T.all isDigit ""` is True. Require at least one digit.+ + Don't emit `<<>>` for spans with no id.+ + Avoid `=` delimiter for inline code containing `=`.+ Org has no escape mechanism inside verbatim text, so `=code+ with ==` did not parse as verbatim. Fall back to the+ equivalent `~...~` delimiter when the content contains `=`+ (and no `~`).+ + Escape square brackets in links. Link targets+ containing square brackets broke the bracket link syntax.+ Escape targets the way Emacs' `org-link-escape` does:+ backslash-escape brackets and double backslash runs occurring+ before a bracket or at the end of the target. Link+ descriptions cannot contain escapes; instead, like+ `org-link-make-string`, insert a zero-width space between+ consecutive closing brackets and before a closing bracket at+ the end of the description.+ + Build escaped strings from chunks, not characters.+ `escapeString` allocated one Doc node per character for any+ string containing a non-alphanumeric character. Split on the+ (rare) special characters instead and emit intervening text+ as single literals. No change in output.+ + Emit definitions for footnotes nested in footnotes.+ + Use a counter for footnote numbers. Computing the+ reference number as `length stNotes + 1` walked the+ accumulated note list for every footnote, making note+ numbering quadratic in the number of notes.++ * Org reader and writer:++ + Make code line comma-escaping match Emacs' behavior.+ Org's escaping rule for code in src/example blocks adds a+ comma to lines matching `^[ \t]*,*(\*|#\+)`, i.e. lines+ already starting with commas before `*` or `#+` get an+ additional comma; unescaping removes one comma from such+ lines. The writer previously left a literal `,#+foo` line+ unescaped, and the reader then stripped its comma when+ reading the result back, corrupting the code on round trips.+ Writer and reader now both handle runs of commas, matching Emacs.++ * Text.Pandoc.ImageSize:++ + ImageType now derives Eq [API change].+ + Fix typo in bare JPEG signature detection.+ + Fix size detection for lossless (VP8L) WebP.+ + Make EMF parsing more robust.+ + Don't let zlib errors escape as exceptions in `pdfSize`.+ Treat malformed streams as a parse scanner (so we keep+ scanning the rest for a `/MediaBox`).+ + Fix AVIF detection and parsing.+ + Take bounding box origin into account for EPS. The+ size was computed from the upper corner alone, giving wrong+ dimensions for EPS files whose bounding box has a non-zero origin.+ + Handle commas and fractional numbers in SVG `viewBox`.+ + Add tests for image type and size detection (Tests.ImageSize).+ + Determine JPEG size without decoding the image (which can+ allocate huge amounts of memory). If the header scan fails,+ we still fall back to the full decoder.+ + Scan PDF object streams in chunks, not byte by byte.+ + Speed up `findSvgTag` by using a single pass. Up to 60X+ faster on files with few `<` characters.+ + Determine PNG size without decoding the image.+ If the header scan fails, we still fall back to the full decoder.+ + Use `writerDpi` for AVIF images instead of hardcoding 72.+ With the default options this changes the assumed resolution from+ 72 to 96 dpi.+ + Allow whitespace between number and unit in `numUnit`.+ So e.g. `width="3 cm"` is now recognized.+ + Handle largesize and size-0 boxes in AVIF parser.+ + Make checkDpi default to 72 for negative dpi values, not+ 0; a negative dpi would produce negative dimensions in+ `sizeInPoints`.++ * Text.Pandoc.SelfContained:++ + Fix inverted charset condition in `makeDataURI`.+ + Keep semicolon in `@import` fallback output.+ + Make `</script` check case-insensitive.+ + Prefix all `url(#...)` occurrences in SVG attributes.+ Previously only the first got prefixed rewritten.+ + Handle gzip decompression errors gracefully.+ + Remove unused `isHtml5` field from ConvertState.+ + Cache fetched resources. Previously every occurrence of a+ resource was fetched, decompressed, and CSS-rewritten+ independently, so a document referencing the same image+ N times triggered N network requests. Failed fetches are+ cached too, so each missing resource is now reported only once.+ + Only add `role` and `aria-label` when inlining SVGs.+ Do not add them to other elements with `src` attributes.+ + Escape only what is needed in textual data URIs.++ * Text.Pandoc.UTF8:++ + Avoid copying input when it contains no CRs. `toText` and+ `toTextLazy `unconditionally ran a CR-removing+ filter over the input, allocating a full copy of the document+ even in the common case where no CRs are present. Check for a+ CR first (B.elem, a fast memchr) and reuse the input buffer+ unchanged if none is found; for the lazy variant, do this+ chunk-wise to preserve laziness. On a 10 MB LF-only input+ this makes `toText` over 4x faster; when CRs are present the+ extra scan is not measurable.+ + Make `readFile` exception-safe. Use `withFile`+ instead of `openFile` so the handle is closed even if reading+ throws.++ * Text.Pandoc.Class:++ + Avoid copying input in `toTextM`. Skip the CR-filtering+ copy when the input contains no CRs, as already done in+ Text.Pandoc.UTF8.toText.+ + Make `runSilently` error-safe. Previously, if the action+ passed to runSilently threw an error that was later caught,+ the verbosity remained pinned at ERROR and all previously+ accumulated log messages were lost. Now the original log+ and verbosity are restored even when the action fails.+ + Fix `isRelativeToParentDir`. Compare the first path+ component rather than just looking at a prefix, to+ correctly handle paths like `..foo/bar.yaml`.+ + Fix base64 detection in `extractURIData`. The base64 indicator+ in a data URI is the final parameter of the media type and may+ follow other parameters, e.g. `charset`. Previously, the+ code expected `;base64` to be the only parameter.+ + Fix percent-decoding in `extractURIData`.+ + Report accurate offset in `toTextM` errors. Scan for the first+ invalid UTF-8 sequence and report its actual position and byte.+ + Reset HTTP manager in `setNoCheckCertificate`. The HTTP manager+ is created lazily with TLS settings based on `stNoCheckCertificate`+ and then cached in CommonState, so changing the option after the+ first request had no effect. Discard the cached manager when the+ option's value changes.+ + Don't follow symlink cycles in `addToFileTree`.+ + Finish factoring `openURL` into Text.Pandoc.Class.IO.HTTP.+ Commit 455bea907 added the new module but did not register it+ in pandoc.cabal or remove the original definitions.+ + `logOutput`: avoid multiple `hPutStrLn`, which can cause confusing+ interleaving.++ * Text.Pandoc.MediaBag:++ + Use hashlazy to avoid copying media contents.+ + Treat `data:` and `file:` URI schemes case-insensitively.+ + Make MediaItem mime type and path strict. `mediaContents`+ is left lazy so contents need not be forced at insert time.+ + Use `Text.Pandoc.URI.isURI` in `canonicalize`. `Network.URI.isURI`+ treats Windows drive-letter paths like `c:/foo.png` as URIs.+ + Only reject `..` as a path component. The insertMedia check used+ `isInfixOf`, so a harmless name like foo..bar.png was silently+ renamed to its content hash.+ + Collapse `.` and `..` components in `canonicalize`. `normalise`+ does not remove redundant path components, so `img/../a.png`+ and `a.png` were distinct keys. Use `makeCanonical` (as+ PandocPure's FileTree already does for its path-indexed map),+ which also handles duplicate and trailing slashes, replacing+ backslashes with slashes first.+ + Prevent `mediaPath` collisions between keys. The friendly+ mediaPath was derived by percent-unescaping the key, so+ distinct keys like `a%20b.png` and `a b.png` produced the same+ mediaPath ("a b.png") and silently clobbered each other on+ extraction (and inside docx/epub archives). Now the original name+ is only kept if the key contains no percent sign, so `mediaPath`+ equals the key and distinct keys yield distinct paths; anything+ percent-encoded gets a content-hash name. Hashed names can+ only coincide for identical contents, which is harmless.++ * Text.Pandoc.XML.Light:++ + Fix escaping of repeated `]]>` in `CDATA`.+ + Make `escStr` more efficient.+ + Avoid round-trips in `ppCDataS` prettify path.+ This makes `showCData` and `ppcCData` unused, so they are+ removed.+ + Parse XML fragments from the event stream instead of using+ xml-conduit's document parser, which requires a single root+ element. Our earlier woraround with a wrapper element was+ fragile. Behavior changes:++ - Content fragments with an XML declaration or DOCTYPE+ followed by multiple root elements, and text-only or empty+ input, now parse instead of erroring.+ - Attributes now preserve document order instead of being sorted+ alphabetically (the document parser stored them in a Map).+ - Errors for unresolved entities and mismatched tags now+ report source positions.++ + Use `text-builder` package for rendering, instead of text's+ lazy Text builder. On a large document this cuts docx conversion+ time by about 13%; output is byte-for-byte identical.+ + Expose `ppcTopElement` from Output.+ + ConfigPP now has a field `inlineTag` that checks for inline+ tags. Inline tags are printed on one line and not indented,+ by default. Export `useInlineTags`, `prettyConfigPP`.++ * Text.Pandoc.Sources:++ + Add bulk `takeWhileP`/`takeWhile1P` combinators [API change].+ Character streams over Sources previously had to be+ consumed one character at a time via `satisfy`, at a cost of+ several allocations and monadic binds per character. The new+ combinators scan a whole run of matching characters with a+ single parser invocation using `T.span`, while replicating the+ exact semantics of `T.pack <$> many/many1 (satisfy f)`,+ including empty-chunk handling, position updates at chunk+ boundaries, and parsec's error messages.+ + Use the new bulk `takeWhileP`/`takeWhile1P` combinators across+ readers.++ * Text.Pandoc.Translations:++ + `setTranslations` now keeps an already-loaded translation+ table when the language is unchanged, instead of+ unconditionally clearing the cache and forcing a re-read and+ re-parse of the translations YAML file on the next `translateTerm`.+ + Term names in translation files are now parsed with a+ precomputed Map lookup instead of the derived Read instance,+ which is very slow for a 22-constructor enum.++ * Text.Pandoc.Chunks:++ + Remove vestigial `nav-path` attribute and `rmNavAttrs` walk.+ This no longer did anything; output is unaffected.+ + Use `compactifyTable` for tables produced by all readers.+ Remove old ad hoc `paraToPlain` at the table cell level.+ This should ensure that we don't get tables that mix Plain+ and Para (#11864). Such tables tend to look funny when rendered in+ docx and other formats.++ * Text.Pandoc.Data:++ + Fix `getDataFileNames` with `-embed_data_files`. Previously+ it was not looking in the right directory and not+ recursing.++ * Text.Pandoc.Shared:++ + `taskListItemFromAscii`: Fix incorrect treatment of `[ ]` as+ checked.+ + Add `stringifyInlines`, a single-pass `stringify` for+ inlines [API change]. This is about 6x faster than `stringify`+ for long inline sequences+ + Speed up `stringify` by making it accumulate `[Text]`+ and concatenate once at the end, instead of mappending at every node.+ + Use `stringifyInlines` instead of `stringify` where possible.+ + Add new function `compactifyTable`. [API change]+ This converts cells that consist in a single Para block to a+ Plain, provided the table contains only such cells (or empty cells).+ + Drop expired entries in `decrementTrailingRowSpans`.+ Entries whose RowSpan fell to 0 were left in the map, relying+ on every consumer to guard against them. Delete them instead.+ + Recognize empty task list items in `toTaskListItem`.+ + Make `endsWithPlain` look inside DefinitionList.+ endsWithPlain recursed into the last item of BulletList and+ OrderedList but ignored DefinitionList, so list items ending+ with a compact definition list were treated as loose by the+ RST, Org, and Haddock writers.+ + Avoid Text -> String round-trips in `htmlAttrs`.+ This adds a FromText constraint to `htmlAttrs` and+ `tagWithAttrs` [API change].+ + Fuse traversals in `ensureValidXmlIdentifiers`.++ * Text.Pandoc.Writers.Shared:++ + Fix logic bug in `splitSentences`.+ + `lookupMetaBool`: treat empty block or inline list as False.+ + `htmlAttrs`: escape id and class attributes, like the others.+ + `stripLeadingTrailingSpace` - strip multiple Space, if present.+ + `toSubscript`: handle minus sign.+ + Fix `ensureValidXmlIdentifiers` for Figure and table+ sub-elements, resolving a bug that produced broken internal+ links in the HTML4/XHTML, EPUB, DocBook, TEI, ICML, FB2, and+ ODT writers.++ * Text.Pandoc.Parsing:++ + Improve performance of `uriScheme` by using a trie. Up to 15%+ faster in URL-heavy documents with `autolink_bare_uris`.+ + Minor code cleanup.+ + Remove `$` checks in math when delim is `\(` or `\\(`.+ + Make `anyOrderedListMarker` more efficient.++ * HTML template:++ + Dark mode support (#11831, Clar Fon).++ * reveal.js template:++ + Fix plugin paths (#11907).++ * flake.nix: parse allow-newer and allow-newer-deps in+ stack.yaml.++ * Make `embed_data_files` flag default to True. Remove flag+ settings from cabal.project. This makes it possible to+ override it on the command line.++ * Depend on commonmark 0.3.1, commonmark-extensions 0.2.7.3,+ commonmark-pandoc 0.3.0.2 (major performance improvements).++ * Depend on released asciidoc 0.1.1 (major performance+ improvements).++ * Use released texmath 0.13.3 (major performance improvements).++ * Depend on released djot 0.1.4.3 (major performance+ improvements).++ * Use released citeproc 0.14 (major performance improvements).++ * Use released doclayout 0.6.++ * Use released zip-archive 0.5 (major performance+ improvements).++ * Use released skylighting-0.15 (major performance+ improvements).++ * Depend on released doctemplates 0.11.1.++ * Depend on released typst 0.12.++ * Require text >= 2.0.++ * Bump upper bound for unicode-data.++ * Allow crypton 2.0.x.++ * Allow Diff 2.0.++ * Add `tools/diff-golden-tests.sh`.++ * Fix `tools/diff-zip.sh` on non-Darwin.++ * Add `tools/benchplot.js`. This creates a nice graph comparing two+ benchmarks.++ * `typst-properties.md`: fix fill syntax in Typst property+ examples (#11855, zenor0).++ * Remove tested-with from cabal file. We tend not to keep it up to date.++ * Fix typo in Lua filter example (#11875, Andonome).++ * Fix a bug in jats-reader.xml (duplicate attribute)+ ## pandoc 3.11 (2026-08-28) * Add `--math-method` option. This replaces (now deprecated but@@ -60,6 +793,11 @@ + Fix treatment of DefaultHighlighting (#11829). * Docx writer:++ + Honor CSL bibliography formatting hints emitted by citeproc (#11871).+ Bibliographies from styles with `hanging-indent="true"` (e.g. APA)+ now get a hanging indent, and CSL `line-spacing`/`entry-spacing`+ are mapped to paragraph spacing properties. + Initialize envLang from `lang` metadata (#11301). This ensures that setting `lang` will affect the whole document.
@@ -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$ $--
@@ -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>
@@ -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 {
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: pandoc-version: 3.11+version: 3.12 build-type: Simple license: GPL-2.0-or-later license-file: COPYING.md@@ -11,7 +11,6 @@ stability: alpha homepage: https://pandoc.org category: Text-tested-with: GHC == 9.6.7, GHC == 9.8.4, GHC == 9.10.3, GHC == 9.12.2 synopsis: Conversion between markup formats description: Pandoc is a Haskell library for converting from one markup format to another. The formats it can handle include@@ -219,6 +218,7 @@ test/command/*.md test/command/*.csl test/command/*.svg+ test/command/2623.odt test/command/7691.docx test/command/9391.docx test/command/9358.docx@@ -281,6 +281,9 @@ test/command/11486/scroll.revealjs test/command/11498.png test/command/11301-styles.opendocument+ test/command/11888/section1/lab.md+ test/command/11888/section2/lab.md+ test/command/11888/section3/lab.md test/asciidoc-reader.adoc test/asciidoc-reader.native test/asciidoc-reader-include.rb@@ -463,7 +466,7 @@ flag embed_data_files Description: Embed data files in binary for relocatable executable.- Default: False+ Default: True flag http Description: Support for fetching resources using HTTP.@@ -498,11 +501,13 @@ library xml-light import: common-options- build-depends: xml >= 1.3.12 && < 1.4,+ build-depends: conduit >= 1.3 && < 1.4,+ xml >= 1.3.12 && < 1.4, xml-conduit >= 1.9.1.1 && < 1.11, xml-types >= 0.3 && < 0.4, containers >= 0.6.0.1 && < 0.9,- text >= 1.1.1.0 && < 2.2+ text >= 2.0 && < 2.2,+ text-builder >= 1.0 && < 1.1 hs-source-dirs: xml-light exposed-modules: Text.Pandoc.XML.Light,@@ -525,17 +530,17 @@ blaze-markup >= 0.8 && < 0.9, bytestring >= 0.9 && < 0.13, case-insensitive >= 1.2 && < 1.3,- citeproc >= 0.13.0.1 && < 0.14,- commonmark >= 0.3 && < 0.4,- commonmark-extensions >= 0.2.7.1 && < 0.3,- commonmark-pandoc >= 0.3 && < 0.4,+ citeproc >= 0.14 && < 0.15,+ commonmark >= 0.3.1 && < 0.4,+ commonmark-extensions >= 0.2.7.3 && < 0.3,+ commonmark-pandoc >= 0.3.0.2 && < 0.4, containers >= 0.6.0.1 && < 0.9,- crypton >= 0.30 && < 1.2,+ crypton >= 0.30 && < 2.1, data-default >= 0.4 && < 0.9, deepseq >= 1.3 && < 1.6,- directory >= 1.2.3 && < 1.4,- doclayout >= 0.5.0.3 && < 0.6,- doctemplates >= 0.11 && < 0.12,+ directory >= 1.2.5 && < 1.4,+ doclayout >= 0.6 && < 0.7,+ doctemplates >= 0.11.1 && < 0.12, emojis >= 0.1.5 && < 0.2, exceptions >= 0.8 && < 0.11, file-embed >= 0.0 && < 0.1,@@ -550,34 +555,32 @@ network-uri >= 2.6 && < 2.8, pandoc-types >= 1.23.1.2 && < 1.24, parsec >= 3.1 && < 3.2,- pretty >= 1.1 && < 1.2,- pretty-show >= 1.10 && < 1.11, process >= 1.2.3 && < 1.7, random >= 1.2 && < 1.4, safe >= 0.3.18 && < 0.4, scientific >= 0.3 && < 0.4,- skylighting >= 0.14.7 && < 0.15,- skylighting-core >= 0.14.7 && < 0.15,+ skylighting >= 0.15 && < 0.16,+ skylighting-core >= 0.15 && < 0.16, split >= 0.2 && < 0.3, syb >= 0.1 && < 0.8, tagsoup >= 0.14.6 && < 0.15, temporary >= 1.1 && < 1.4,- texmath >= 0.13.2.2 && < 0.14,- text >= 1.1.1.0 && < 2.2,+ texmath >= 0.13.3 && < 0.14,+ text >= 2.0 && < 2.2, text-conversions >= 0.3 && < 0.4, time >= 1.5 && < 1.17, unicode-collation >= 0.1.1 && < 0.2,- unicode-data >= 0.6 && < 0.9,+ unicode-data >= 0.6 && < 0.10, unicode-transforms >= 0.3 && < 0.5, yaml >= 0.11 && < 0.12, libyaml >= 0.1.4 && < 0.2,- zip-archive >= 0.4.3.1 && < 0.5,- zlib >= 0.5 && < 0.8,+ zip-archive >= 0.5 && < 0.6,+ zlib >= 0.6 && < 0.8, xml >= 1.3.12 && < 1.4,- typst >= 0.11.0.1 && < 0.12,+ typst >= 0.12 && < 0.13, vector >= 0.12 && < 0.14,- djot >= 0.1.4.2 && < 0.2,- asciidoc >= 0.1.0.5 && < 0.2+ djot >= 0.1.4.3 && < 0.2,+ asciidoc >= 0.1.1 && < 0.2 if !os(windows) build-depends: unix >= 2.4 && < 2.9@@ -724,6 +727,7 @@ Text.Pandoc.App.Opt, Text.Pandoc.App.OutputSettings, Text.Pandoc.Class.CommonState,+ Text.Pandoc.Class.IO.HTTP, Text.Pandoc.Class.PandocMonad, Text.Pandoc.Class.PandocIO, Text.Pandoc.Class.PandocPure,@@ -771,7 +775,6 @@ Text.Pandoc.Readers.Mdoc.Standards, Text.Pandoc.Readers.Typst.Parsing, Text.Pandoc.Readers.Typst.Math,- Text.Pandoc.Readers.ODT.Base, Text.Pandoc.Readers.ODT.Namespaces, Text.Pandoc.Readers.ODT.StyleReader, Text.Pandoc.Readers.ODT.ContentReader,@@ -780,8 +783,6 @@ Text.Pandoc.Readers.ODT.Generic.Utils, Text.Pandoc.Readers.ODT.Generic.Namespaces, Text.Pandoc.Readers.ODT.Generic.XMLConverter,- Text.Pandoc.Readers.ODT.Arrows.State,- Text.Pandoc.Readers.ODT.Arrows.Utils, Text.Pandoc.Readers.Org.BlockStarts, Text.Pandoc.Readers.Org.Blocks, Text.Pandoc.Readers.Org.DocumentTree,@@ -843,12 +844,12 @@ main-is: test-pandoc.hs hs-source-dirs: test build-depends: pandoc,- Diff >= 0.2 && < 1.1,+ Diff >= 0.2 && < 2.1, Glob >= 0.7 && < 0.11, bytestring >= 0.9 && < 0.13, containers >= 0.4.2.1 && < 0.9,- directory >= 1.2.3 && < 1.4,- doctemplates >= 0.11 && < 0.12,+ directory >= 1.2.5 && < 1.4,+ doctemplates >= 0.11.1 && < 0.12, filepath >= 1.1 && < 1.6, mtl >= 2.2 && < 2.4, pandoc-types >= 1.23.1 && < 1.24,@@ -857,17 +858,18 @@ tasty-golden >= 2.3 && < 2.4, tasty-hunit >= 0.9 && < 0.11, tasty-quickcheck >= 0.8 && < 0.12,- text >= 1.1.1.0 && < 2.2,+ text >= 2.0 && < 2.2, temporary >= 1.1 && < 1.4, time >= 1.5 && < 1.17, xml >= 1.3.12 && < 1.4,- zip-archive >= 0.4.3 && < 0.5+ zip-archive >= 0.5 && < 0.6 other-modules: Tests.Old Tests.Command Tests.Helpers Tests.Shared Tests.MediaBag Tests.XML+ Tests.ImageSize Tests.Readers.LaTeX Tests.Readers.HTML Tests.Readers.JATS@@ -934,7 +936,7 @@ build-depends: bytestring, tasty-bench >= 0.4 && <= 0.5, mtl >= 2.2 && < 2.4,- text >= 1.1.1.0 && < 2.2,+ text >= 2.0 && < 2.2, deepseq -- we increase heap size to avoid benchmarking garbage collection: ghc-options: -rtsopts -with-rtsopts=-A8m -threaded
@@ -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
@@ -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
@@ -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
@@ -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 $
@@ -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)
@@ -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
@@ -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
@@ -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@.
@@ -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
@@ -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@
@@ -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
@@ -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,
@@ -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`
@@ -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''
@@ -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
@@ -188,7 +188,6 @@ ParsecT, SourcePos, SourceName,- setSourceName, Column, Line, incSourceLine,
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -15,7 +15,7 @@ ) where -import Control.Monad (mzero, when)+import Control.Monad (mzero, when, guard) import Data.Text (Text) import Text.Parsec ((<|>), ParsecT, Stream(..), notFollowedBy, many1, try) import Text.Pandoc.Options@@ -44,8 +44,10 @@ (try (string "text" >> (("\\text" <>) <$> inBalancedBraces 0 "")) <|> (\c -> T.pack ['\\',c]) <$> anyChar))- <|> ("\n" <$ blankline <* notFollowedBy' blankline <* notFollowedBy (char '$'))- <|> (T.pack <$> many1 spaceChar <* notFollowedBy (char '$'))+ <|> ("\n" <$ blankline <* notFollowedBy' blankline <*+ (guard (op /= "$") <|> notFollowedBy (char '$')))+ <|> (T.pack <$> many1 spaceChar <*+ (guard (op /= "$") <|> notFollowedBy (char '$'))) ) (try $ textStr cl) notFollowedBy digit -- to prevent capture of $5 return $ trimMath $ T.concat words'
@@ -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 ->
@@ -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'') =
@@ -91,7 +91,7 @@ where content = brackets <|> line brackets = try $ option "" (T.singleton <$> newline)- <+> (char ' ' >> (manyChar (char ' ') <+> textStr "}}}") <* eol)+ <+> (char ' ' >> (takeWhileP (== ' ') <+> textStr "}}}") <* eol) line = option "" (T.singleton <$> newline) <+> manyTillChar anyChar eol eol = lookAhead $ try $ nowikiEnd <|> newline nowikiStart = optional newline >> string "{{{" >> skipMany spaceChar >> newline@@ -215,8 +215,8 @@ (orig, src) <- wikiImg return $ B.image src "" (B.str orig) where- linkSrc = manyChar $ noneOf "|}\n\r\t"- linkDsc = char '|' >> manyChar (noneOf "}\n\r\t")+ linkSrc = takeWhileP (`notElem` ("|}\n\r\t" :: [Char]))+ linkDsc = char '|' >> takeWhileP (`notElem` ("}\n\r\t" :: [Char])) wikiImg = try $ do string "{{" src <- linkSrc@@ -229,11 +229,11 @@ (orig, src) <- uriLink <|> wikiLink return $ B.link src "" orig where- linkSrc = manyChar $ noneOf "|]\n\r\t"+ linkSrc = takeWhileP (`notElem` ("|]\n\r\t" :: [Char])) linkDsc :: PandocMonad m => Text -> CRLParser m B.Inlines linkDsc otxt = B.str <$> try (option otxt- (char '|' >> manyChar (noneOf "]\n\r\t")))+ (char '|' >> takeWhileP (`notElem` ("]\n\r\t" :: [Char])))) linkImg = try $ char '|' >> image wikiLink = try $ do string "[["
@@ -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)
@@ -80,7 +80,6 @@ import qualified Data.Map as M import qualified Data.Text as T import Data.Maybe (isJust, fromMaybe, mapMaybe)-import Data.Sequence (ViewL (..), viewl) import qualified Data.Sequence as Seq import qualified Data.Set as Set import Citeproc (ItemId(..), Val(TextVal,FancyVal), Reference(..), CitationItem(..))@@ -267,8 +266,10 @@ parPartToText :: ParPart -> T.Text parPartToText (PlainRun run) = runToText run-parPartToText (InternalHyperLink _ children) = T.concat $ map parPartToText children-parPartToText (ExternalHyperLink _ children) = T.concat $ map parPartToText children+parPartToText (InternalHyperLink _ _ children) =+ T.concat $ map parPartToText children+parPartToText (ExternalHyperLink _ _ children) =+ T.concat $ map parPartToText children parPartToText _ = "" blacklistedCharStyles :: [CharStyleName]@@ -411,14 +412,15 @@ AllChanges -> do blks <- smushBlocks <$> mapM bodyPartToBlocks bodyParts ils <- blocksToInlinesWarn cmtId blks- let attr = ("", ["comment-start"], ("id", cmtId) : addAuthorAndDate author date)+ let attr = ("", ["comment-start"], ("comment-id", cmtId) :+ addAuthorAndDate author date) return $ spanWith attr ils _ -> return mempty parPartToInlines' (CommentEnd cmtId) = do opts <- asks docxOptions case readerTrackChanges opts of AllChanges -> do- let attr = ("", ["comment-end"], [("id", cmtId)])+ let attr = ("", ["comment-end"], [("comment-id", cmtId)]) return $ spanWith attr mempty _ -> return mempty parPartToInlines' (BookMark _ anchor) | anchor `elem` dummyAnchors =@@ -461,19 +463,20 @@ return $ spanWith ("", ["chart"], []) $ text "[CHART]" parPartToInlines' Diagram = return $ spanWith ("", ["diagram"], []) $ text "[DIAGRAM]"-parPartToInlines' (InternalHyperLink anchor children) = do+parPartToInlines' (InternalHyperLink anchor tooltip children) = do ils <- smushInlines <$> mapM parPartToInlines' children- return $ link ("#" <> anchor) "" ils-parPartToInlines' (ExternalHyperLink target children) = do+ return $ link ("#" <> anchor) tooltip ils+parPartToInlines' (ExternalHyperLink target tooltip children) = do ils <- smushInlines <$> mapM parPartToInlines' children- return $ link target "" ils+ return $ link target tooltip ils parPartToInlines' (PlainOMath exps) = return $ math $ writeTeX exps parPartToInlines' (OMathPara exps) = return $ displayMath $ writeTeX exps parPartToInlines' (Field info children) = case info of- HyperlinkField url -> parPartToInlines' $ ExternalHyperLink url children+ HyperlinkField url ->+ parPartToInlines' $ ExternalHyperLink url "" children IndexrefField ie -> pure $ spanWith ("",["indexref"], (("entry", entryTitle ie) :@@ -481,8 +484,10 @@ ++ maybe [] (\x -> [("yomi",x)]) (entryYomi ie) ++ [("bold","") | entryBold ie] ++ [("italic","") | entryItalic ie])) mempty- PagerefField fieldAnchor True -> parPartToInlines' $ InternalHyperLink fieldAnchor children- CrossrefField fieldAnchor True -> parPartToInlines' $ InternalHyperLink fieldAnchor children+ PagerefField fieldAnchor True ->+ parPartToInlines' $ InternalHyperLink fieldAnchor "" children+ CrossrefField fieldAnchor True ->+ parPartToInlines' $ InternalHyperLink fieldAnchor "" children EndNoteCite t -> do formattedCite <- smushInlines <$> mapM parPartToInlines' children opts <- asks docxOptions@@ -605,18 +610,10 @@ return $ Header n (newIdent, classes, kvs) ils makeHeaderAnchor' blk = return blk --- Rewrite a standalone paragraph block as a plain-singleParaToPlain :: Blocks -> Blocks-singleParaToPlain blks- | (Para ils :< seeq) <- viewl $ unMany blks- , Seq.null seeq =- singleton $ Plain ils-singleParaToPlain blks = blks- cellToCell :: PandocMonad m => RowSpan -> Docx.Cell -> DocxContext m Pandoc.Cell cellToCell rowSpan (Docx.Cell align gridSpan _ bps) = do blks <- smushBlocks <$> mapM bodyPartToBlocks bps- let blks' = singleParaToPlain $ fromList $ blocksToDefinitions $ blocksToBullets $ toList blks+ let blks' = fromList $ blocksToDefinitions $ blocksToBullets $ toList blks return (cell (convertAlign align) rowSpan (ColSpan (fromIntegral gridSpan)) blks') @@ -860,7 +857,8 @@ let attr = case mbsty of Just sty | extStylesEnabled -> ("", [], [("custom-style", sty)]) _ -> nullAttr- return $ tableWith attr cap'+ return $ compactifyTable+ $ tableWith attr cap' (zip alignments widths) (TableHead nullAttr headerCells) [TableBody nullAttr 0 [] bodyCells]
@@ -398,8 +398,8 @@ | CommentStart CommentId Author (Maybe CommentDate) [BodyPart] | CommentEnd CommentId | BookMark BookMarkId Anchor- | InternalHyperLink Anchor [ParPart]- | ExternalHyperLink URL [ParPart]+ | InternalHyperLink Anchor T.Text [ParPart] -- tooltip+ | ExternalHyperLink URL T.Text [ParPart] -- tooltip | Drawing FilePath T.Text T.Text B.ByteString Extent -- title, alt | Chart -- placeholder for now | Diagram -- placeholder for now@@ -1217,18 +1217,20 @@ location <- asks envLocation children <- mconcat <$> mapD (elemToParPart ns) (elChildren element) rels <- asks envRelationships+ let tooltip = fromMaybe "" $ findAttrByName ns "w" "tooltip" element case lookupRelationship location relId rels of Just target -> case findAttrByName ns "w" "anchor" element of Just anchor -> return- [ExternalHyperLink (target <> "#" <> anchor) children]- Nothing -> return [ExternalHyperLink target children]- Nothing -> return [ExternalHyperLink "" children]+ [ExternalHyperLink (target <> "#" <> anchor) tooltip children]+ Nothing -> return [ExternalHyperLink target tooltip children]+ Nothing -> return [ExternalHyperLink "" tooltip children] elemToParPart' ns element | isElem ns "w" "hyperlink" element , Just anchor <- findAttrByName ns "w" "anchor" element = do children <- mconcat <$> mapD (elemToParPart ns) (elChildren element)- return [InternalHyperLink anchor children]+ let tooltip = fromMaybe "" $ findAttrByName ns "w" "tooltip" element+ return [InternalHyperLink anchor tooltip children] elemToParPart' ns element | isElem ns "w" "commentRangeStart" element , Just cmtId <- findAttrByName ns "w" "id" element = do
@@ -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]
@@ -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>
@@ -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.
@@ -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 =
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 "‘"))
@@ -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
@@ -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)
@@ -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
@@ -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
@@ -23,7 +23,7 @@ import Control.Monad import Control.Monad.Except (throwError) import qualified Data.Bifunctor as Bifunctor-import Data.Char (isAlphaNum, isPunctuation, isSpace)+import Data.Char (isAlphaNum, isDigit, isLetter, isPunctuation, isSpace) import Data.List (transpose, elemIndex, sortOn) import qualified Data.List as L import qualified Data.Map as M@@ -53,7 +53,8 @@ import Text.Pandoc.Readers.HTML.TagCategories (voidTags) import Text.Pandoc.Readers.LaTeX (applyMacros, rawLaTeXBlock, rawLaTeXInline) import Text.Pandoc.Shared-import Text.Pandoc.URI (escapeURI, isURI, pBase64DataURI)+import Text.Pandoc.URI (escapeURI, pBase64DataURI)+import Network.URI (isURI) import Text.Pandoc.XML (fromEntities) import Text.Pandoc.Readers.Metadata (yamlBsToMeta, yamlBsToRefs, yamlMetaBlock) -- import Debug.Trace (traceShowId)@@ -412,7 +413,7 @@ char c notFollowedBy spaces let pEnder = try $ char c >> notFollowedBy (satisfy isAlphaNum)- let regChunk = many1Char (noneOf ['\\','\n','&',c]) <|> litChar+ let regChunk = takeWhile1P (`notElem` ['\\','\n','&',c]) <|> litChar let nestedChunk = (\x -> (c `T.cons` x) `T.snoc` c) <$> quotedTitle c T.unwords . T.words . T.concat <$> manyTill (nestedChunk <|> regChunk) pEnder @@ -660,7 +661,7 @@ identifierAttr :: PandocMonad m => MarkdownParser m (Attr -> Attr) identifierAttr = try $ do char '#'- result <- T.pack <$> many1 (alphaNum <|> oneOf "-_:.") -- see #7920+ result <- takeWhile1P (\x -> isAlphaNum x || x `elem` ("-_:." :: [Char])) -- see #7920 return $ \(_,cs,kvs) -> (result,cs,kvs) classAttr :: PandocMonad m => MarkdownParser m (Attr -> Attr)@@ -814,12 +815,13 @@ blockQuote :: PandocMonad m => MarkdownParser m (F Blocks) blockQuote = do+ pos <- getPosition raw <- emailBlockQuote (mbAlert, raw') <- (do guardEnabled Ext_alerts case raw of (t:ts) | "[!" `T.isPrefixOf` t ->- case T.strip t of+ case T.toUpper (T.strip t) of "[!TIP]" -> pure (Just "tip", ts) "[!WARNING]" -> pure (Just "warning", ts) "[!IMPORTANT]" -> pure (Just "important", ts)@@ -829,12 +831,13 @@ _ -> pure (Nothing, raw)) <|> pure (Nothing, raw) -- parse the extracted block, which may contain various block elements:- contents <- parseFromString' parseBlocks $ T.intercalate "\n" raw' <> "\n\n"+ contents <- parseFromString' (setPosition pos >> parseBlocks)+ $ T.intercalate "\n" raw' <> "\n\n" return $ case mbAlert of Nothing -> B.blockQuote <$> contents Just alert ->- (B.divWith ("", [alert], [])+ (B.divWith ("", ["alert", alert], []) . (B.divWith ("", ["title"], []) (B.para (B.str (T.toTitle alert))) <>)) <$> contents @@ -859,7 +862,7 @@ skipNonindentSpaces notFollowedBy $ string "p." >> spaceChar >> digit -- page number (do guardDisabled Ext_fancy_lists- start <- many1Char digit >>= safeRead+ start <- takeWhile1P isDigit >>= safeRead char '.' gobbleSpaces 1 <|> () <$ lookAhead newline optional $ try (gobbleAtMostSpaces 3 >> notFollowedBy spaceChar)@@ -971,11 +974,12 @@ state <- getState let oldContext = stateParserContext state setState $ state {stateParserContext = ListItemState}+ pos <- getPosition (first, continuationIndent) <- rawListItem fourSpaceRule start continuations <- many (listContinuation continuationIndent) -- parse the extracted block, which may contain various block elements: let raw = T.concat (first:continuations)- contents <- parseFromString' parseBlocks raw+ contents <- parseFromString' (setPosition pos >> parseBlocks) raw updateState (\st -> st {stateParserContext = oldContext}) exts <- getOption readerExtensions return $ B.fromList . taskListItemFromAscii exts . B.toList <$> contents@@ -1016,8 +1020,9 @@ definitionListItem :: PandocMonad m => MarkdownParser m (F (Inlines, [Blocks])) definitionListItem = try $ do+ pos <- getPosition rawLine' <- anyLine- term <- parseFromString' (trimInlinesF <$> inlines) rawLine'+ term <- parseFromString' (setPosition pos >> (trimInlinesF <$> inlines)) rawLine' isTight <- (False <$ blanklines) <|> pure True fourSpaceRule <- (True <$ guardEnabled Ext_four_space_rule) <|> pure False contents <- many1 $ listItem fourSpaceRule defListStart@@ -1223,8 +1228,9 @@ lineBlock = do guardEnabled Ext_line_blocks try $ do+ pos <- getPosition lines' <- lineBlockLines >>=- mapM (parseFromString' (trimInlinesF <$> inlines))+ mapM (parseFromString' (setPosition pos >> (trimInlinesF <$> inlines))) return $ B.lineBlock <$> sequence lines' --@@ -1311,8 +1317,9 @@ tableLine :: PandocMonad m => [Int] -> MarkdownParser m (F [Blocks])-tableLine indices = rawTableLine indices >>=- fmap sequence . mapM (parseFromString' (mconcat <$> many plain))+tableLine indices = do+ raw <- rawTableLine indices+ sequence <$> mapM (parseFromString' (mconcat <$> many plain)) raw -- Parse a multiline table row and return a list of blocks (columns). multilineRow :: PandocMonad m@@ -1397,7 +1404,7 @@ then [] else map (T.unlines . map trim) rawHeadsList heads <- fmap sequence $- mapM (parseFromString' (mconcat <$> many plain).trim) rawHeads+ mapM (parseFromString' (mconcat <$> many plain) . trim) rawHeads return (fmap (:[]) heads, aligns, indices') -- Parse a grid table: starts with row of '-' on top, then header@@ -1533,7 +1540,7 @@ return $ do caption' <- caption (TableComponents _attr _capt colspecs th tb tf) <- tableComponents- return $ B.tableWith attr+ return $ compactifyTable $ B.tableWith attr (B.simpleCaption $ B.plain caption') colspecs th tb tf --@@ -1647,8 +1654,8 @@ skipSpaces result <- trim . T.concat <$> manyTill- ( many1Char (noneOf "`\n")- <|> many1Char (char '`')+ ( takeWhile1P (\c -> c /= '`' && c /= '\n')+ <|> takeWhile1P (== '`') <|> (char '\n' >> notFollowedBy (inList >> listStart) >> notFollowedBy' blankline@@ -1683,7 +1690,7 @@ guardDisabled Ext_intraword_underscores <|> guard (c == '*') <|> (guard =<< notAfterString)- cs <- many1Char (char c)+ cs <- takeWhile1P (== c) (return (B.str cs) <>) <$> whitespace <|> case T.length cs of@@ -1783,7 +1790,7 @@ mmdShortSubscript = try $ do guardEnabled Ext_short_subsuperscripts char '~'- result <- T.pack <$> many1 alphaNum+ result <- takeWhile1P isAlphaNum return $ return $ B.str result whitespace :: PandocMonad m => MarkdownParser m (F Inlines)@@ -1798,7 +1805,7 @@ str :: PandocMonad m => MarkdownParser m (F Inlines) str = do !result <- mconcat <$> many1- ( T.pack <$> (many1 alphaNum)+ ( takeWhile1P isAlphaNum <|> "." <$ try (char '.' <* notFollowedBy (char '.')) ) updateLastStrPos (do guardEnabled Ext_smart@@ -1862,7 +1869,8 @@ try parenthesizedChars <|> (notFollowedBy (oneOf "\n\r )") >> litChar) <|> (lookAhead (oneOf "\n\r") >> notFollowedBy linkTitle' >> litChar)- <|> try (many1Char spaceChar <* notFollowedBy (oneOf "\"')"))+ <|> try (takeWhile1P (\x -> x == ' ' || x == '\t')+ <* notFollowedBy (oneOf "\"')")) let sourceURL = T.unwords . T.words . T.concat <$> many urlChunk src <- try (litBetween '<' '>') <|> try base64DataURI <|> sourceURL tit <- option "" linkTitle'@@ -1872,13 +1880,21 @@ base64DataURI :: PandocMonad m => ParsecT Sources s m Text base64DataURI = do- Sources ((pos, txt):rest) <- getInput- let r = A.parse (fst <$> A.match pBase64DataURI) txt- case r of- A.Done remaining consumed -> do- let pos' = incSourceColumn pos (T.length consumed)- setInput $ Sources ((pos', remaining):rest)- return consumed+ inp <- getInput+ case inp of+ Sources ((pos, txt):rest) ->+ -- feed mempty to force a result if attoparsec returns Partial:+ case A.feed (A.parse (fst <$> A.match pBase64DataURI) txt) mempty of+ A.Done remaining consumed -> do+ -- keep the chunk's position unchanged: chunk positions are+ -- invariant (see uncons in T.P.Sources), and withRaw's+ -- sourcesDifference relies on this. Instead, advance parsec's+ -- own position (a data URI cannot contain newlines):+ setInput $ Sources ((pos, remaining):rest)+ curPos <- getPosition+ setPosition $ incSourceColumn curPos (T.length consumed)+ return consumed+ _ -> mzero _ -> mzero linkTitle :: PandocMonad m => MarkdownParser m Text@@ -2021,6 +2037,20 @@ bareURL = do guardEnabled Ext_autolink_bare_uris getState >>= guard . stateAllowLinks+ -- Fast rejection: a bare URI must contain ':' (after the scheme) and+ -- an email address '@', in both cases before any whitespace, since+ -- neither can contain whitespace. So if the whitespace-delimited+ -- token ahead contains neither ':' nor '@', both parsers must fail.+ -- (If the token extends beyond the current input chunk, we skip the+ -- check and just try the parsers.)+ inp <- getInput+ case unSources inp of+ (_,t):_ ->+ case T.find (\c -> isSpace c || c == ':' || c == '@') t of+ Just ':' -> return ()+ Just '@' -> return ()+ _ -> mzero+ [] -> return () try $ do (cls, (orig, src)) <- (("uri",) <$> uri) <|> (("email",) <$> emailAddress) notFollowedBy $ try $ spaces >> htmlTag (~== TagClose ("a" :: Text))@@ -2050,7 +2080,9 @@ isFragment = T.take 1 path == "#" path' = T.unpack path isAbsolutePath = Posix.isAbsolute path' || Windows.isAbsolute path'- in if T.null path || isFragment || isAbsolutePath || isURI path+ in if T.null path || isFragment || isAbsolutePath || isURI (T.unpack path)+ -- note: we use Network.URI.isURI instead of T.P.URI.isURI+ -- because it doesn't whitelist schemes; see #11858. then path else case takeDirectory fp of@@ -2121,7 +2153,7 @@ rawConTeXtEnvironment = try $ do string "\\start" completion <- inBrackets (letter <|> digit <|> spaceChar)- <|> many1Char letter+ <|> takeWhile1P isLetter !contents <- manyTill (rawConTeXtEnvironment <|> countChar 1 anyChar) (try $ string "\\stop" >> textStr completion) return $! "\\start" <> completion <> T.concat contents <> "\\stop" <> completion@@ -2174,7 +2206,9 @@ string ":::" skipMany (char ':') skipMany spaceChar- attribs <- attributes <|> ((\x -> ("",[x],[])) <$> many1Char nonspaceChar)+ attribs <- attributes <|> ((\x -> ("",[x],[])) <$>+ takeWhile1P (\x -> x /= ' ' && x /= '\t' &&+ x /= '\n' && x /= '\r')) skipMany spaceChar skipMany (char ':') blankline@@ -2219,7 +2253,7 @@ guardEnabled Ext_emoji try $ do char ':'- emojikey <- many1Char (alphaNum <|> oneOf "_+-")+ emojikey <- takeWhile1P (\x -> isAlphaNum x || x `elem` ("_+-" :: [Char])) char ':' case emojiToInline emojikey of Just i -> return (return $ B.singleton i)
@@ -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 ()
@@ -142,7 +142,7 @@ mdocToken = lexComment <|> lexControlLine <|> lexTextLine lexMacroName :: PandocMonad m => Lexer m T.Text-lexMacroName = many1Char (satisfy isMacroChar)+lexMacroName = takeWhile1P isMacroChar where isMacroChar '%' = True isMacroChar x = isAlphaNum x
@@ -37,8 +37,8 @@ import Text.Pandoc.Options import Text.Pandoc.Parsing hiding (tableCaption) import Text.Pandoc.Readers.HTML (htmlTag, isCommentTag, toAttr)-import Text.Pandoc.Shared (formatCode, safeRead, splitTextBy, stringify,- stripTrailingNewlines, trim, tshow)+import Text.Pandoc.Shared (formatCode, safeRead, splitTextBy, stringifyInlines,+ stripTrailingNewlines, trim, tshow, compactifyTable) import Text.Pandoc.XML (fromEntities) -- | Read mediawiki from an input string and return a Pandoc document.@@ -299,7 +299,8 @@ else ([], hdr:rows') let toRow = Row nullAttr toHeaderRow l = [toRow l | not (null l)]- return $ B.table (B.simpleCaption $ B.plain caption)+ return $ compactifyTable+ $ B.table (B.simpleCaption $ B.plain caption) cellspecs (TableHead nullAttr $ toHeaderRow headers) [TableBody nullAttr 0 [] $ map toRow rows]@@ -324,7 +325,7 @@ char '=' skipMany spaceChar v <- (char '"' >> manyTillChar (satisfy (/='\n')) (char '"'))- <|> many1Char (satisfy $ \c -> not (isSpace c) && c /= '|')+ <|> takeWhile1P (\c -> not (isSpace c) && c /= '|') return (k,v) tableStart :: PandocMonad m => MWParser m ()@@ -406,7 +407,8 @@ string "{{" notFollowedBy (char '{') lookAhead $ letter <|> digit <|> char ':'- let chunk = template <|> variable <|> many1Char (noneOf "{}") <|> countChar 1 anyChar+ let chunk = template <|> variable <|> takeWhile1P (\c -> c /= '{' && c /= '}')+ <|> countChar 1 anyChar contents <- manyTill chunk (try $ string "}}") return $ "{{" <> T.concat contents <> "}}" @@ -614,7 +616,7 @@ <|> special str :: PandocMonad m => MWParser m Inlines-str = B.str <$> many1Char (noneOf $ specialChars ++ spaceChars)+str = B.str <$> takeWhile1P (`notElem` (specialChars ++ spaceChars)) math :: PandocMonad m => MWParser m Inlines math = (B.displayMath . trim <$> try (many1 (char ':') >> textInTags "math"))@@ -711,9 +713,9 @@ image = try $ do sym "[[" imageIdentifier- fname <- addUnderscores <$> many1Char (noneOf "|]")+ fname <- addUnderscores <$> takeWhile1P (\c -> c /= '|' && c /= ']') _ <- many imageOption- dims <- try (char '|' *> sepBy (manyChar digit) (char 'x') <* string "px")+ dims <- try (char '|' *> sepBy (takeWhileP isDigit) (char 'x') <* string "px") <|> return [] _ <- many imageOption let kvs = case dims of@@ -723,7 +725,7 @@ let attr = ("", [], kvs) caption <- (B.str fname <$ sym "]]") <|> try (char '|' *> (mconcat <$> manyTill inline (sym "]]")))- return $ B.imageWith attr fname (stringify caption) caption+ return $ B.imageWith attr fname (stringifyInlines caption) caption imageOption :: PandocMonad m => MWParser m Text imageOption = try $ char '|' *> opt@@ -744,7 +746,7 @@ internalLink :: PandocMonad m => MWParser m Inlines internalLink = try $ do sym "[["- pagename <- T.unwords . T.words <$> manyChar (noneOf "|]")+ pagename <- T.unwords . T.words <$> takeWhileP (\c -> c /= '|' && c /= ']') label <- option (B.text pagename) $ char '|' *> ( (mconcat <$> many1 (notFollowedBy (char ']') *> inline)) -- the "pipe trick"@@ -752,8 +754,8 @@ <|> return (B.text $ T.drop 1 $ T.dropWhile (/=':') pagename) ) sym "]]" -- see #8525:- linktrail <- B.text <$> manyChar (satisfy (\c -> isLetter c && not (isCJK c)))- let link = B.linkWith (mempty, ["wikilink"], mempty) (addUnderscores pagename) (stringify label) (label <> linktrail)+ linktrail <- B.text <$> takeWhileP (\c -> isLetter c && not (isCJK c))+ let link = B.linkWith (mempty, ["wikilink"], mempty) (addUnderscores pagename) (stringifyInlines label) (label <> linktrail) if "Category:" `T.isPrefixOf` pagename then do updateState $ \st -> st{ mwCategoryLinks = link : mwCategoryLinks st }
@@ -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")
@@ -103,7 +103,7 @@ let media = filteredFilesFromArchive archive filePathIsODTMedia let startState = readerState styles media either (\_ -> Left $ PandocParseError "Could not convert opendocument") Right- (runConverter' read_body startState contentElem)+ (runConverter read_body startState contentElem) --
@@ -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
@@ -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`
@@ -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
@@ -1,10 +1,7 @@-{-# LANGUAGE Arrows #-}-{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE OverloadedStrings #-} {- |@@ -24,9 +21,7 @@ , read_body ) where -import Prelude hiding (Applicative(..))-import Control.Applicative hiding (liftA, liftA2, liftA3)-import Control.Arrow+import Control.Applicative ((<|>)) import Control.Monad ((<=<)) import qualified Data.ByteString.Lazy as B@@ -48,14 +43,10 @@ import Text.Pandoc.Readers.Docx.Combine (combineBlocks) -import Text.Pandoc.Readers.ODT.Base import Text.Pandoc.Readers.ODT.Namespaces import Text.Pandoc.Readers.ODT.StyleReader -import Text.Pandoc.Readers.ODT.Arrows.State (foldS)-import Text.Pandoc.Readers.ODT.Arrows.Utils-import Text.Pandoc.Readers.ODT.Generic.Fallible-import Text.Pandoc.Readers.ODT.Generic.Utils+import Text.Pandoc.Readers.ODT.Generic.Utils (findBy) import Text.Pandoc.Readers.ODT.Generic.XMLConverter import Network.URI (parseRelativeReference, URI(uriPath))@@ -125,13 +116,6 @@ shiftListLevel diff = modifyListLevel (+ diff) ---swapCurrentListStyle :: Maybe ListStyle -> ReaderState- -> (ReaderState, Maybe ListStyle)-swapCurrentListStyle mListStyle state = ( state { currentListStyle = mListStyle }- , currentListStyle state- )---- lookupPrettyAnchor :: Anchor -> ReaderState -> Maybe Anchor lookupPrettyAnchor anchor ReaderState{..} = M.lookup anchor bookmarkAnchors @@ -158,85 +142,57 @@ -- Reader type and associated tools -------------------------------------------------------------------------------- -type ODTReader a b = XMLReader ReaderState a b--type ODTReaderSafe a b = XMLReaderSafe ReaderState a b+type ODTReader a = XMLConverter Namespace ReaderState a --- | Extract something from the styles-fromStyles :: (a -> Styles -> b) -> ODTReaderSafe a b-fromStyles f = keepingTheValue- (getExtraState >>^ styleSet)- >>% f+-- | Extract the styles from the reader state+getStyles :: ODTReader Styles+getStyles = styleSet <$> getExtraState ---getStyleByName :: ODTReader StyleName Style-getStyleByName = fromStyles lookupStyle >>^ maybeToChoice+getStyleByName :: StyleName -> ODTReader Style+getStyleByName name = getStyles >>= fromMaybeF . lookupStyle name ---findStyleFamily :: ODTReader Style StyleFamily-findStyleFamily = fromStyles getStyleFamily >>^ maybeToChoice- ---lookupListStyle :: ODTReader StyleName ListStyle-lookupListStyle = fromStyles lookupListStyleByName >>^ maybeToChoice+lookupListStyle :: StyleName -> ODTReader ListStyle+lookupListStyle name = getStyles >>= fromMaybeF . lookupListStyleByName name ---switchCurrentListStyle :: ODTReaderSafe (Maybe ListStyle) (Maybe ListStyle)-switchCurrentListStyle = keepingTheValue getExtraState- >>% swapCurrentListStyle- >>> first setExtraState- >>^ snd+switchCurrentListStyle :: Maybe ListStyle -> ODTReader (Maybe ListStyle)+switchCurrentListStyle mListStyle = do+ state <- getExtraState+ setExtraState $ state { currentListStyle = mListStyle }+ return $ currentListStyle state ---pushStyle :: ODTReaderSafe Style Style-pushStyle = keepingTheValue (- ( keepingTheValue getExtraState- >>% pushStyle'- )- >>> setExtraState- )- >>^ fst+pushStyle :: Style -> ODTReader ()+pushStyle style = modifyExtraState (pushStyle' style) ---popStyle :: ODTReaderSafe x x-popStyle = keepingTheValue (- getExtraState- >>> arr popStyle'- >>> setExtraState- )- >>^ fst+popStyle :: ODTReader ()+popStyle = modifyExtraState popStyle' ---getCurrentListLevel :: ODTReaderSafe a ListLevel-getCurrentListLevel = getExtraState >>^ currentListLevel+getCurrentListLevel :: ODTReader ListLevel+getCurrentListLevel = currentListLevel <$> getExtraState ---getListContinuationStartCounters :: ODTReaderSafe a (M.Map ListLevel Int)-getListContinuationStartCounters = getExtraState >>^ listContinuationStartCounters-+getPreviousListStartCounter :: ListLevel -> ODTReader Int+getPreviousListStartCounter listLevel =+ M.findWithDefault 0 listLevel . listContinuationStartCounters+ <$> getExtraState ---getPreviousListStartCounter :: ODTReaderSafe ListLevel Int-getPreviousListStartCounter = proc listLevel -> do- counts <- getListContinuationStartCounters -< ()- returnA -< M.findWithDefault 0 listLevel counts+updateMediaWithResource :: (FilePath, B.ByteString) -> ODTReader ()+updateMediaWithResource resource = modifyExtraState (insertMedia' resource) ---updateMediaWithResource :: ODTReaderSafe (FilePath, B.ByteString) (FilePath, B.ByteString)-updateMediaWithResource = keepingTheValue (- (keepingTheValue getExtraState- >>% insertMedia'- )- >>> setExtraState- )- >>^ fst--lookupResource :: ODTReaderSafe FilePath (FilePath, B.ByteString)-lookupResource = proc target -> do- state <- getExtraState -< ()- case lookup target (getMediaEnv state) of- Just bs -> returnV (target, bs) -<< ()- Nothing -> returnV ("", B.empty) -< ()+lookupResource :: FilePath -> ODTReader (FilePath, B.ByteString)+lookupResource target = do+ state <- getExtraState+ case lookup target (getMediaEnv state) of+ Just bs -> return (target, bs)+ Nothing -> return ("", B.empty) type AnchorPrefix = T.Text @@ -255,77 +211,58 @@ -- | First argument: basis for a new "pretty" anchor if none exists yet -- Second argument: a key ("ugly" anchor) -- Returns: saved "pretty" anchor or created new one-getPrettyAnchor :: ODTReaderSafe (AnchorPrefix, Anchor) Anchor-getPrettyAnchor = proc (baseIdent, uglyAnchor) -> do- state <- getExtraState -< ()+getPrettyAnchor :: AnchorPrefix -> Anchor -> ODTReader Anchor+getPrettyAnchor baseIdent uglyAnchor = do+ state <- getExtraState case lookupPrettyAnchor uglyAnchor state of- Just prettyAnchor -> returnA -< prettyAnchor+ Just prettyAnchor -> return prettyAnchor Nothing -> do let newPretty = uniqueIdentFrom baseIdent (usedAnchors state)- modifyExtraState (putPrettyAnchor uglyAnchor newPretty) -<< newPretty+ modifyExtraState (putPrettyAnchor uglyAnchor newPretty)+ return newPretty -- | Input: basis for a new header anchor -- Output: saved new anchor-getHeaderAnchor :: ODTReaderSafe Inlines Anchor-getHeaderAnchor = proc title -> do- state <- getExtraState -< ()+getHeaderAnchor :: Inlines -> ODTReader Anchor+getHeaderAnchor title = do+ state <- getExtraState let exts = extensionsFromList [Ext_auto_identifiers] let anchor = uniqueIdent exts (toList title) (Set.fromList $ usedAnchors state)- modifyExtraState (putPrettyAnchor anchor anchor) -<< anchor+ modifyExtraState (putPrettyAnchor anchor anchor)+ return anchor -------------------------------------------------------------------------------- -- Working with styles -------------------------------------------------------------------------------- ----readStyleByName :: ODTReader a (StyleName, Style)-readStyleByName =- findAttr NsText "style-name" >>? keepingTheValue getStyleByName >>^ liftE- where- liftE :: (StyleName, Fallible Style) -> Fallible (StyleName, Style)- liftE (name, Right v) = Right (name, v)- liftE (_, Left v) = Left v-----isStyleToTrace :: ODTReader Style Bool-isStyleToTrace = findStyleFamily >>?^ (==FaText)+-- | Read the style referenced by the current element's+-- style-name attribute. Fails if there is no such attribute or if the+-- style cannot be found.+readStyleByName :: ODTReader (StyleName, Style)+readStyleByName = do+ name <- findAttr NsText "style-name"+ style <- getStyleByName name+ return (name, style) ---withNewStyle :: ODTReaderSafe x Inlines -> ODTReaderSafe x Inlines-withNewStyle a = proc x -> do- fStyle <- readStyleByName -< ()+withNewStyle :: ODTReader Inlines -> ODTReader Inlines+withNewStyle reader = do+ fStyle <- tryC readStyleByName case fStyle of- Right (styleName, _) | isCodeStyle styleName -> do- inlines <- a -< x- arr inlineCode -<< inlines- Right (_, style) -> do- mFamily <- arr styleFamily -< style- fTextProps <- arr ( maybeToChoice- . textProperties- . styleProperties- ) -< style- case fTextProps of- Right textProps -> do- state <- getExtraState -< ()- let triple = (state, textProps, mFamily)- modifier <- arr modifierFromStyleDiff -< triple- fShouldTrace <- isStyleToTrace -< style- case fShouldTrace of- Right shouldTrace ->- if shouldTrace- then do- pushStyle -< style- inlines <- a -< x- popStyle -< ()- arr modifier -<< inlines- else- -- In case anything goes wrong- a -< x- Left _ -> a -< x- Left _ -> a -< x- Left _ -> a -< x+ Right (styleName, _) | isCodeStyle styleName ->+ inlineCode <$> reader+ Right (_, style)+ | Just textProps <- textProperties (styleProperties style) -> do+ state <- getExtraState+ let mFamily = styleFamily style+ modifier = modifierFromStyleDiff (state, textProps, mFamily)+ pushStyle style+ inlines <- reader+ popStyle+ return $ modifier inlines+ _ -> reader where isCodeStyle :: StyleName -> Bool isCodeStyle "Source_Text" = True@@ -333,7 +270,7 @@ isCodeStyle _ = False inlineCode :: Inlines -> Inlines- inlineCode = code . T.concat . map stringify . toList+ inlineCode = code . stringifyInlines type PropertyTriple = (ReaderState, TextProperties, Maybe StyleFamily) type InlineModifier = Inlines -> Inlines@@ -342,16 +279,15 @@ -- an instance of 'Inlines' modifierFromStyleDiff :: PropertyTriple -> InlineModifier modifierFromStyleDiff propertyTriple =- composition $+ foldr (.) id $ getVPosModifier propertyTriple- : map (first ($ propertyTriple) >>> ifThen_else ignore)+ : map (\(hasChanged', modifier) ->+ if hasChanged' propertyTriple then modifier else ignore) [ (hasEmphChanged , emph ) , (hasChanged isStrong , strong ) , (hasChanged strikethrough , strikeout ) ] where- ifThen_else else' (if',then') = if if' then then' else else'- ignore = id :: InlineModifier getVPosModifier :: PropertyTriple -> InlineModifier@@ -367,9 +303,9 @@ getVPosModifier' ( _ , _ ) = ignore hasEmphChanged :: PropertyTriple -> Bool- hasEmphChanged = swing any [ hasChanged isEmphasised- , hasChanged underline- ]+ hasEmphChanged triple = any ($ triple) [ hasChanged isEmphasised+ , hasChanged underline+ ] hasChanged property triple@(_, property -> newProperty, _) = (/= Just newProperty) (lookupPreviousValue property triple)@@ -414,20 +350,18 @@ = False ---constructPara :: ODTReaderSafe a Blocks -> ODTReaderSafe a Blocks-constructPara reader = proc blocks -> do- fStyle <- readStyleByName -< blocks+constructPara :: ODTReader Blocks -> ODTReader Blocks+constructPara reader = do+ fStyle <- tryC readStyleByName case fStyle of- Left _ -> reader -< blocks- Right (styleName, _) | isTableCaptionStyle styleName -> do- blocks' <- reader -< blocks- arr tableCaptionP -< blocks'+ Left _ -> reader+ Right (styleName, _) | isTableCaptionStyle styleName ->+ tableCaptionP <$> reader Right (_, style) -> do- props <- fromStyles extendedStylePropertyChain -< [style]- listLevel <- getCurrentListLevel -< ()- let modifier = getParaModifier listLevel props- blocks' <- reader -< blocks- arr modifier -<< blocks'+ styles <- getStyles+ let props = extendedStylePropertyChain [style] styles+ listLevel <- getCurrentListLevel+ getParaModifier listLevel props <$> reader where isTableCaptionStyle :: StyleName -> Bool isTableCaptionStyle "Table" = True@@ -466,78 +400,72 @@ -- Then prepares the state for eventual child lists and constructs the list from -- the results. -- Two main cases are handled: The list may provide its own style or it may--- rely on a parent list's style. I the former case the current style in the+-- rely on a parent list's style. In the former case the current style in the -- state must be switched before and after the call to the child converter -- while in the latter the child converter can be called directly. -- If anything goes wrong, a default ordered-list-constructor is used.-constructList :: ODTReaderSafe x [Blocks] -> ODTReaderSafe x Blocks-constructList reader = proc x -> do- modifyExtraState (shiftListLevel 1) -< ()- listLevel <- getCurrentListLevel -< ()- listContinuationStartCounter <- getPreviousListStartCounter -< listLevel- fStyleName <- findAttr NsText "style-name" -< ()- fContNumbering <- findAttr NsText "continue-numbering" -< ()- listItemCount <- reader >>^ length -< x+constructList :: ODTReader [Blocks] -> ODTReader Blocks+constructList reader = do+ modifyExtraState (shiftListLevel 1)+ listLevel <- getCurrentListLevel+ listContinuationStartCounter <- getPreviousListStartCounter listLevel+ fStyleName <- tryC (findAttr NsText "style-name")+ fContNumbering <- tryC (findAttr NsText "continue-numbering") - let continueNumbering = case fContNumbering of- Right "true" -> True- _ -> False+ let continueNumbering = fContNumbering == Right "true" - let startNumForListLevelStyle = listStartingNumber continueNumbering listContinuationStartCounter- let defaultOrderedListConstructor = constructOrderedList (startNumForListLevelStyle Nothing) listLevel listItemCount+ startNumForListLevelStyle mListLevelStyle+ | continueNumbering = listContinuationStartCounter+ | isJust mListLevelStyle = listItemStart (fromJust mListLevelStyle)+ | otherwise = 1 + constructWith startNum constructor = do+ items <- reader+ modifyExtraState (shiftListLevel (-1))+ modifyExtraState (modifyListContinuationStartCounter listLevel+ (startNum + length items))+ return $ constructor items++ constructOrderedList =+ let startNum = startNumForListLevelStyle Nothing+ in constructWith startNum+ (orderedListWith (startNum, DefaultStyle, DefaultDelim))++ constructListWith listLevelStyle =+ let startNum = startNumForListLevelStyle (Just listLevelStyle)+ in constructWith startNum (getListConstructor listLevelStyle startNum)+ case fStyleName of Right styleName -> do- fListStyle <- lookupListStyle -< styleName+ fListStyle <- tryC (lookupListStyle styleName) case fListStyle of- Right listStyle -> do- fListLevelStyle <- arr (uncurry getListLevelStyle) -< (listLevel, listStyle)- case fListLevelStyle of+ Right listStyle ->+ case getListLevelStyle listLevel listStyle of Just listLevelStyle -> do- let startNum = startNumForListLevelStyle $ Just listLevelStyle- oldListStyle <- switchCurrentListStyle -< Just listStyle- blocks <- constructListWith listLevelStyle startNum listLevel listItemCount -<< x- switchCurrentListStyle -< oldListStyle- returnA -< blocks- Nothing -> defaultOrderedListConstructor -<< x- Left _ -> defaultOrderedListConstructor -<< x+ oldListStyle <- switchCurrentListStyle (Just listStyle)+ blocks <- constructListWith listLevelStyle+ _ <- switchCurrentListStyle oldListStyle+ return blocks+ Nothing -> constructOrderedList+ Left _ -> constructOrderedList Left _ -> do- state <- getExtraState -< ()- mListStyle <- arr currentListStyle -< state+ mListStyle <- currentListStyle <$> getExtraState case mListStyle of- Just listStyle -> do- fListLevelStyle <- arr (uncurry getListLevelStyle) -< (listLevel, listStyle)- case fListLevelStyle of- Just listLevelStyle -> do- let startNum = startNumForListLevelStyle $ Just listLevelStyle- constructListWith listLevelStyle startNum listLevel listItemCount -<< x- Nothing -> defaultOrderedListConstructor -<< x- Nothing -> defaultOrderedListConstructor -<< x- where- listStartingNumber continueNumbering listContinuationStartCounter mListLevelStyle- | continueNumbering = listContinuationStartCounter- | isJust mListLevelStyle = listItemStart (fromJust mListLevelStyle)- | otherwise = 1- constructOrderedList startNum listLevel listItemCount =- reader- >>> modifyExtraState (shiftListLevel (-1))- >>> modifyExtraState (modifyListContinuationStartCounter listLevel (startNum + listItemCount))- >>^ orderedListWith (startNum, DefaultStyle, DefaultDelim)- constructListWith listLevelStyle startNum listLevel listItemCount =- reader- >>> getListConstructor listLevelStyle startNum- ^>> modifyExtraState (shiftListLevel (-1))- >>> modifyExtraState (modifyListContinuationStartCounter listLevel (startNum + listItemCount))+ Just listStyle ->+ case getListLevelStyle listLevel listStyle of+ Just listLevelStyle -> constructListWith listLevelStyle+ Nothing -> constructOrderedList+ Nothing -> constructOrderedList -------------------------------------------------------------------------------- -- Readers -------------------------------------------------------------------------------- -type ElementMatcher result = (Namespace, ElementName, ODTReader result result)+type Matcher result = ElementMatcher Namespace ReaderState result -type InlineMatcher = ElementMatcher Inlines+type InlineMatcher = Matcher Inlines -type BlockMatcher = ElementMatcher Blocks+type BlockMatcher = Matcher Blocks newtype FirstMatch a = FirstMatch (Alt Maybe a) deriving (Foldable, Monoid, Semigroup)@@ -554,33 +482,14 @@ mempty = CombiningBlocks mempty ---matchingElement :: (Monoid e)- => Namespace -> ElementName- -> ODTReaderSafe e e- -> ElementMatcher e-matchingElement ns name reader = (ns, name, asResultAccumulator reader)- where- asResultAccumulator :: (ArrowChoice a, Monoid m) => a m m -> a m (Fallible m)- asResultAccumulator a = liftAsSuccess $ keepingTheValue a >>% mappend-----matchChildContent' :: (Monoid result)- => [ElementMatcher result]- -> ODTReaderSafe a result-matchChildContent' ls = returnV mempty >>> matchContent' ls-----matchSmushedChildBlocks' :: [ElementMatcher CombiningBlocks] -> ODTReaderSafe a Blocks-matchSmushedChildBlocks' ls = liftA unCombiningBlocks- $ returnV mempty- >>> matchContent' ls+matchingElement :: Namespace -> ElementName+ -> ODTReader e+ -> Matcher e+matchingElement ns name reader = (ns, name, reader) ---matchChildContent :: (Monoid result)- => [ElementMatcher result]- -> ODTReaderSafe (result, XML.Content) result- -> ODTReaderSafe a result-matchChildContent ls fallback = returnV mempty >>> matchContent ls fallback+matchSmushedChildBlocks' :: [Matcher CombiningBlocks] -> ODTReader Blocks+matchSmushedChildBlocks' ls = unCombiningBlocks <$> matchContent' ls -------------------------------------------- -- Matchers@@ -591,94 +500,83 @@ ---------------------- ----- | Open Document allows several consecutive spaces if they are marked up-read_plain_text :: ODTReaderSafe (Inlines, XML.Content) Inlines-read_plain_text = fst ^&&& read_plain_text' >>% recover- where- -- fallible version- read_plain_text' :: ODTReader (Inlines, XML.Content) Inlines- read_plain_text' = ( second ( arr extractText )- >>^ spreadChoice >>?! second text- )- >>?% mappend- --- extractText :: XML.Content -> Fallible T.Text- extractText (XML.Text cData) = succeedWith (XML.cdData cData)- extractText _ = failEmpty+-- | Open Document allows several consecutive spaces if they are marked up.+-- Text content of the current element is read with this fallback converter.+read_plain_text :: XML.Content -> ODTReader Inlines+read_plain_text (XML.Text cData) = return $ text $ XML.cdData cData+read_plain_text _ = return mempty read_text_seq :: InlineMatcher read_text_seq = matchingElement NsText "sequence"- $ matchChildContent [] read_plain_text+ $ matchContent [] read_plain_text -- specifically. I honor that, although the current implementation of 'mappend' -- for 'Inlines' in "Text.Pandoc.Builder" will collapse them again. -- The rational is to be prepared for future modifications. read_spaces :: InlineMatcher-read_spaces = matchingElement NsText "s" (- readAttrWithDefault NsText "c" 1 -- how many spaces?- >>^ fromList.(`replicate` Space)- )+read_spaces = matchingElement NsText "s" $ do+ count <- readAttrWithDefault NsText "c" 1 -- how many spaces?+ return $ fromList (replicate count Space) -- read_line_break :: InlineMatcher read_line_break = matchingElement NsText "line-break"- $ returnV linebreak+ $ return linebreak -- read_tab :: InlineMatcher read_tab = matchingElement NsText "tab"- $ returnV space+ $ return space -- read_span :: InlineMatcher read_span = matchingElement NsText "span" $ withNewStyle- $ matchChildContent [ read_span- , read_spaces- , read_line_break- , read_tab- , read_link- , read_frame- , read_note- , read_citation- , read_bookmark- , read_bookmark_start- , read_reference_start- , read_bookmark_ref- , read_reference_ref- ] read_plain_text+ $ matchContent [ read_span+ , read_spaces+ , read_line_break+ , read_tab+ , read_link+ , read_frame+ , read_note+ , read_citation+ , read_bookmark+ , read_bookmark_start+ , read_reference_start+ , read_bookmark_ref+ , read_reference_ref+ ] read_plain_text ---read_paragraph :: ElementMatcher CombiningBlocks-read_paragraph = matchingElement NsText "p" $- liftA CombiningBlocks $ proc blocks -> do- fStyle <- readStyleByName -< blocks- case fStyle of- Right style | isPreformattedStyle style -> do- liftA (codeBlock . stringify) $ matchParagraphContent -< blocks- _ ->- constructPara $ liftA para $ withNewStyle matchParagraphContent -< blocks- where- isPreformattedStyle :: (StyleName, Style) -> Bool- isPreformattedStyle ("Preformatted_20_Text", _) = True- isPreformattedStyle (_, Style { styleParentName = Just "Preformatted_20_Text" }) = True- isPreformattedStyle _ = False+read_paragraph :: Matcher CombiningBlocks+read_paragraph = matchingElement NsText "p" $ fmap CombiningBlocks $ do+ fStyle <- tryC readStyleByName+ case fStyle of+ Right style | isPreformattedStyle style ->+ codeBlock . stringifyInlines <$> matchParagraphContent+ _ ->+ constructPara (para <$> withNewStyle matchParagraphContent)+ where+ isPreformattedStyle :: (StyleName, Style) -> Bool+ isPreformattedStyle ("Preformatted_20_Text", _) = True+ isPreformattedStyle (_, Style { styleParentName = Just "Preformatted_20_Text" }) = True+ isPreformattedStyle _ = False -matchParagraphContent :: ODTReaderSafe a Inlines-matchParagraphContent = matchChildContent [ read_span- , read_spaces- , read_line_break- , read_tab- , read_link- , read_note- , read_citation- , read_bookmark- , read_bookmark_start- , read_reference_start- , read_bookmark_ref- , read_reference_ref- , read_frame- , read_text_seq- ] read_plain_text+matchParagraphContent :: ODTReader Inlines+matchParagraphContent = matchContent [ read_span+ , read_spaces+ , read_line_break+ , read_tab+ , read_link+ , read_note+ , read_citation+ , read_bookmark+ , read_bookmark_start+ , read_reference_start+ , read_bookmark_ref+ , read_reference_ref+ , read_frame+ , read_text_seq+ ] read_plain_text ----------------------@@ -686,72 +584,69 @@ ---------------------- ---read_header :: ElementMatcher CombiningBlocks-read_header = matchingElement NsText "h"- $ proc blocks -> do- level <- ( readAttrWithDefault NsText "outline-level" 1- ) -< blocks- children <- ( matchChildContent [ read_span- , read_spaces- , read_line_break- , read_tab- , read_link- , read_note- , read_citation- , read_bookmark- , read_bookmark_start- , read_reference_start- , read_bookmark_ref- , read_reference_ref- , read_frame- ] read_plain_text- ) -< blocks- anchor <- getHeaderAnchor -< children+read_header :: Matcher CombiningBlocks+read_header = matchingElement NsText "h" $ do+ level <- readAttrWithDefault NsText "outline-level" 1+ children <- matchContent [ read_span+ , read_spaces+ , read_line_break+ , read_tab+ , read_link+ , read_note+ , read_citation+ , read_bookmark+ , read_bookmark_start+ , read_reference_start+ , read_bookmark_ref+ , read_reference_ref+ , read_frame+ ] read_plain_text+ anchor <- getHeaderAnchor children let idAttr = (anchor, [], []) -- no classes, no key-value pairs- arr (CombiningBlocks . uncurry3 headerWith) -< (idAttr, level, children)+ return $ CombiningBlocks $ headerWith idAttr level children ---------------------- -- Lists ---------------------- ---read_list :: ElementMatcher CombiningBlocks+read_list :: Matcher CombiningBlocks read_list = matchingElement NsText "list"- $ liftA CombiningBlocks- $ constructList- $ matchChildContent' [ read_list_item- , read_list_header- ]+ $ CombiningBlocks+ <$> constructList+ ( matchContent' [ read_list_item+ , read_list_header+ ] ) ---read_list_item :: ElementMatcher [Blocks]+read_list_item :: Matcher [Blocks] read_list_item = read_list_element "list-item" -read_list_header :: ElementMatcher [Blocks]+read_list_header :: Matcher [Blocks] read_list_header = read_list_element "list-header" -read_list_element :: ElementName -> ElementMatcher [Blocks]+read_list_element :: ElementName -> Matcher [Blocks] read_list_element listElement = matchingElement NsText listElement- $ liftA (compactify.(:[]))- ( matchSmushedChildBlocks' [ read_paragraph- , read_header- , read_list- , read_section- ]- )+ $ compactify . (:[])+ <$> matchSmushedChildBlocks'+ [ read_paragraph+ , read_header+ , read_list+ , read_section+ ] ---------------------- -- Sections ---------------------- -read_section :: ElementMatcher CombiningBlocks+read_section :: Matcher CombiningBlocks read_section = matchingElement NsText "section"- $ liftA (CombiningBlocks . divWith nullAttr)- $ matchSmushedChildBlocks' [ read_paragraph- , read_header- , read_list- , read_table- , read_section- ]+ $ CombiningBlocks . divWith nullAttr+ <$> matchSmushedChildBlocks' [ read_paragraph+ , read_header+ , read_list+ , read_table+ , read_section+ ] ----------------------@@ -760,19 +655,19 @@ read_link :: InlineMatcher read_link = matchingElement NsText "a"- $ liftA3 link- ( findAttrTextWithDefault NsXLink "href" ""- >>> arr fixRelativeLink )- ( findAttrTextWithDefault NsOffice "title" "" )- ( matchChildContent [ read_span- , read_note- , read_citation- , read_bookmark- , read_bookmark_start- , read_reference_start- , read_bookmark_ref- , read_reference_ref- ] read_plain_text )+ $ link+ <$> (fixRelativeLink+ <$> findAttrWithDefault NsXLink "href" "")+ <*> findAttrWithDefault NsOffice "title" ""+ <*> matchContent [ read_span+ , read_note+ , read_citation+ , read_bookmark+ , read_bookmark_start+ , read_reference_start+ , read_bookmark_ref+ , read_reference_ref+ ] read_plain_text fixRelativeLink :: T.Text -> T.Text fixRelativeLink uri =@@ -789,8 +684,7 @@ read_note :: InlineMatcher read_note = matchingElement NsText "note"- $ liftA note- $ matchChildContent' [ read_note_body ]+ $ note <$> matchContent' [ read_note_body ] read_note_body :: BlockMatcher read_note_body = matchingElement NsText "note-body"@@ -802,12 +696,11 @@ read_citation :: InlineMatcher read_citation = matchingElement NsText "bibliography-mark"- $ liftA2 cite- ( liftA2 makeCitation- ( findAttrTextWithDefault NsText "identifier" "" )- ( readAttrWithDefault NsText "number" 0 )- )- ( matchChildContent [] read_plain_text )+ $ cite+ <$> ( makeCitation+ <$> findAttrWithDefault NsText "identifier" ""+ <*> readAttrWithDefault NsText "number" 0 )+ <*> matchContent [] read_plain_text where makeCitation :: T.Text -> Int -> [Citation] makeCitation citeId num = [Citation citeId [] [] NormalCitation num 0]@@ -818,16 +711,16 @@ ---------------------- ---read_table :: ElementMatcher CombiningBlocks+read_table :: Matcher CombiningBlocks read_table = matchingElement NsTable "table"- $ liftA (CombiningBlocks . table')- $ (matchChildContent' [read_table_header]) &&&- (matchChildContent' [read_table_row])+ $ fmap (CombiningBlocks . table')+ $ (,) <$> matchContent' [read_table_header]+ <*> matchContent' [read_table_row] -- | A table without a caption. table' :: ([[Cell]], [[Cell]]) -> Blocks-table' (headers, rows) =- table emptyCaption (replicate numcols defaults) th [tb] tf+table' (headers, rows) = compactifyTable $+ table emptyCaption (replicate numcols defaults) th [tb] tf where defaults = (AlignDefault, ColWidthDefault) numcols = maximum $ map length $ headers ++ rows@@ -837,27 +730,27 @@ tf = TableFoot nullAttr [] ---read_table_header :: ElementMatcher [[Cell]]+read_table_header :: Matcher [[Cell]] read_table_header = matchingElement NsTable "table-header-rows"- $ matchChildContent' [ read_table_row- ]+ $ matchContent' [ read_table_row+ ] ---read_table_row :: ElementMatcher [[Cell]]+read_table_row :: Matcher [[Cell]] read_table_row = matchingElement NsTable "table-row"- $ liftA (:[])- $ matchChildContent' [ read_table_cell- ]+ $ (:[])+ <$> matchContent' [ read_table_cell+ ] ---read_table_cell :: ElementMatcher [Cell]+read_table_cell :: Matcher [Cell] read_table_cell = matchingElement NsTable "table-cell"- $ liftA3 cell'- (readAttrWithDefault NsTable "number-rows-spanned" 1 >>^ RowSpan)- (readAttrWithDefault NsTable "number-columns-spanned" 1 >>^ ColSpan)- $ matchSmushedChildBlocks' [ read_paragraph- , read_list- ]+ $ cell'+ <$> (RowSpan <$> readAttrWithDefault NsTable "number-rows-spanned" 1)+ <*> (ColSpan <$> readAttrWithDefault NsTable "number-columns-spanned" 1)+ <*> matchSmushedChildBlocks' [ read_paragraph+ , read_list+ ] where cell' rowSpan colSpan blocks = map (cell AlignDefault rowSpan colSpan) $ compactify [blocks] @@ -867,39 +760,39 @@ -- read_frame :: InlineMatcher-read_frame = matchingElement NsDraw "frame"- $ filterChildrenName' NsDraw (`elem` ["image", "object", "text-box"])- >>> foldS read_frame_child- >>> arr fold+read_frame = matchingElement NsDraw "frame" $ do+ children <- filterChildrenName' NsDraw (`elem` ["image", "object", "text-box"])+ fold . mconcat <$> mapM read_frame_child children -read_frame_child :: ODTReaderSafe XML.Element (FirstMatch Inlines)-read_frame_child =- proc child -> case elName child of- "image" -> read_frame_img -< child- "object" -> read_frame_mathml -< child- "text-box" -> read_frame_text_box -< child- _ -> returnV mempty -< ()+read_frame_child :: XML.Element -> ODTReader (FirstMatch Inlines)+read_frame_child child =+ case elName child of+ "image" -> read_frame_img child+ "object" -> read_frame_mathml child+ "text-box" -> read_frame_text_box child+ _ -> return mempty -read_frame_img :: ODTReaderSafe XML.Element (FirstMatch Inlines)-read_frame_img =- proc img -> do- src <- executeIn (findAttr' NsXLink "href") -< img- case fold src of- "" -> returnV mempty -< ()- src' -> do- let exts = extensionsFromList [Ext_auto_identifiers]- src'' = fixRelativeLink src'- resource <- lookupResource -< T.unpack src''- _ <- updateMediaWithResource -< resource- w <- findAttrText' NsSVG "width" -< ()- h <- findAttrText' NsSVG "height" -< ()- titleNodes <- matchChildContent' [ read_frame_title ] -< ()- alt <- matchChildContent [] read_plain_text -< ()- arr (firstMatch . uncurry4 imageWith) -<- (image_attributes w h, src'', inlineListToIdentifier exts (toList titleNodes), alt)+read_frame_img :: XML.Element -> ODTReader (FirstMatch Inlines)+read_frame_img img = do+ src <- executeIn img (findAttr' NsXLink "href")+ case fold src of+ "" -> return mempty+ src' -> do+ let exts = extensionsFromList [Ext_auto_identifiers]+ src'' = fixRelativeLink src'+ resource <- lookupResource (T.unpack src'')+ updateMediaWithResource resource+ w <- findAttr' NsSVG "width"+ h <- findAttr' NsSVG "height"+ titleNodes <- matchContent' [ read_frame_title ]+ alt <- matchContent [] read_plain_text+ return $ firstMatch+ $ imageWith (image_attributes w h) src''+ (inlineListToIdentifier exts (toList titleNodes))+ alt read_frame_title :: InlineMatcher-read_frame_title = matchingElement NsSVG "title" (matchChildContent [] read_plain_text)+read_frame_title = matchingElement NsSVG "title" (matchContent [] read_plain_text) image_attributes :: Maybe T.Text -> Maybe T.Text -> Attr image_attributes x y =@@ -909,24 +802,23 @@ dim name (Just v) = [(name, v)] dim _ Nothing = [] -read_frame_mathml :: ODTReaderSafe XML.Element (FirstMatch Inlines)-read_frame_mathml =- proc obj -> do- src <- executeIn (findAttr' NsXLink "href") -< obj- case fold src of- "" -> returnV mempty -< ()- src' -> do- let path = T.unpack $- fromMaybe src' (T.stripPrefix "./" src') <> "/content.xml"- (_, mathml) <- lookupResource -< path- case readMathML (UTF8.toText $ B.toStrict mathml) of- Left _ -> returnV mempty -< ()- Right exps -> arr (firstMatch . displayMath . writeTeX) -< exps+read_frame_mathml :: XML.Element -> ODTReader (FirstMatch Inlines)+read_frame_mathml obj = do+ src <- executeIn obj (findAttr' NsXLink "href")+ case fold src of+ "" -> return mempty+ src' -> do+ let path = T.unpack $+ fromMaybe src' (T.stripPrefix "./" src') <> "/content.xml"+ (_, mathml) <- lookupResource path+ case readMathML (UTF8.toText $ B.toStrict mathml) of+ Left _ -> return mempty+ Right exps -> return $ firstMatch $ displayMath $ writeTeX exps -read_frame_text_box :: ODTReaderSafe XML.Element (FirstMatch Inlines)-read_frame_text_box = proc box -> do- paragraphs <- executeIn (matchSmushedChildBlocks' [ read_paragraph ]) -< box- arr read_img_with_caption -< toList paragraphs+read_frame_text_box :: XML.Element -> ODTReader (FirstMatch Inlines)+read_frame_text_box box = do+ paragraphs <- executeIn box (matchSmushedChildBlocks' [ read_paragraph ])+ return $ read_img_with_caption $ toList paragraphs read_img_with_caption :: [Block] -> FirstMatch Inlines read_img_with_caption (Para [Image attr alt (src,title)] : _) =@@ -946,26 +838,20 @@ _ANCHOR_PREFIX_ = "anchor" ---readAnchorAttr :: ODTReader a Anchor-readAnchorAttr = findAttrText NsText "name"+readAnchorAttr :: ODTReader Anchor+readAnchorAttr = findAttr NsText "name" -- | Beware: may fail-findAnchorName :: ODTReader AnchorPrefix Anchor-findAnchorName = ( keepingTheValue readAnchorAttr- >>^ spreadChoice- ) >>?! getPrettyAnchor-+findAnchorName :: AnchorPrefix -> ODTReader Anchor+findAnchorName anchorPrefix = do+ uglyAnchor <- readAnchorAttr+ getPrettyAnchor anchorPrefix uglyAnchor ---maybeAddAnchorFrom :: ODTReader Inlines AnchorPrefix- -> ODTReaderSafe Inlines Inlines+maybeAddAnchorFrom :: ODTReader AnchorPrefix -> ODTReader Inlines maybeAddAnchorFrom anchorReader =- keepingTheValue (anchorReader >>? findAnchorName >>?^ toAnchorElem)- >>>- proc (inlines, fAnchorElem) -> do- case fAnchorElem of- Right anchorElem -> returnA -< anchorElem- Left _ -> returnA -< inlines+ (toAnchorElem <$> (anchorReader >>= findAnchorName))+ <|> return mempty where toAnchorElem :: Anchor -> Inlines toAnchorElem anchorID = spanWith (anchorID, [], []) mempty@@ -974,12 +860,12 @@ -- read_bookmark :: InlineMatcher read_bookmark = matchingElement NsText "bookmark"- $ maybeAddAnchorFrom (liftAsSuccess $ returnV _ANCHOR_PREFIX_)+ $ maybeAddAnchorFrom (return _ANCHOR_PREFIX_) -- read_bookmark_start :: InlineMatcher read_bookmark_start = matchingElement NsText "bookmark-start"- $ maybeAddAnchorFrom (liftAsSuccess $ returnV _ANCHOR_PREFIX_)+ $ maybeAddAnchorFrom (return _ANCHOR_PREFIX_) -- read_reference_start :: InlineMatcher@@ -987,20 +873,18 @@ $ maybeAddAnchorFrom readAnchorAttr -- | Beware: may fail-findAnchorRef :: ODTReader a Anchor-findAnchorRef = ( findAttrText NsText "ref-name"- >>?^ (_ANCHOR_PREFIX_,)- ) >>?! getPrettyAnchor-+findAnchorRef :: ODTReader Anchor+findAnchorRef = do+ uglyAnchor <- findAttr NsText "ref-name"+ getPrettyAnchor _ANCHOR_PREFIX_ uglyAnchor ---maybeInAnchorRef :: ODTReaderSafe Inlines Inlines-maybeInAnchorRef = proc inlines -> do- fRef <- findAnchorRef -< ()+maybeInAnchorRef :: Inlines -> ODTReader Inlines+maybeInAnchorRef inlines = do+ fRef <- tryC findAnchorRef case fRef of- Right anchor ->- arr (toAnchorRef anchor) -<< inlines- Left _ -> returnA -< inlines+ Right anchor -> return $ toAnchorRef anchor inlines+ Left _ -> return inlines where toAnchorRef :: Anchor -> Inlines -> Inlines toAnchorRef anchor = link ("#" <> anchor) "" -- no title@@ -1008,28 +892,25 @@ -- read_bookmark_ref :: InlineMatcher read_bookmark_ref = matchingElement NsText "bookmark-ref"- $ maybeInAnchorRef- <<< matchChildContent [] read_plain_text+ $ matchContent [] read_plain_text >>= maybeInAnchorRef -- read_reference_ref :: InlineMatcher read_reference_ref = matchingElement NsText "reference-ref"- $ maybeInAnchorRef- <<< matchChildContent [] read_plain_text+ $ matchContent [] read_plain_text >>= maybeInAnchorRef ---------------------- -- Entry point ---------------------- -read_text :: ODTReaderSafe a Pandoc-read_text = matchSmushedChildBlocks' [ read_header- , read_paragraph- , read_list- , read_section- , read_table- ]- >>^ doc+read_text :: ODTReader Pandoc+read_text = doc <$> matchSmushedChildBlocks' [ read_header+ , read_paragraph+ , read_list+ , read_section+ , read_table+ ] post_process :: Pandoc -> Pandoc post_process (Pandoc m blocks) =@@ -1040,11 +921,10 @@ = Table attr (Caption Nothing blks) specs th tb tf : post_process' xs post_process' bs = bs -read_body :: ODTReader a (Pandoc, MediaBag)+read_body :: ODTReader (Pandoc, MediaBag) read_body = executeInSub NsOffice "body" $ executeInSub NsOffice "text"- $ liftAsSuccess- $ proc inlines -> do- txt <- read_text -< inlines- state <- getExtraState -< ()- returnA -< (post_process txt, getMediaBag state)+ $ do+ txt <- read_text+ state <- getExtraState+ return (post_process txt, getMediaBag state)
@@ -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
@@ -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'
@@ -1,8 +1,4 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE PatternGuards #-} {- | Module : Text.Pandoc.Readers.ODT.Generic.XMLConverter Copyright : Copyright (C) 2015 Martin Linnemann@@ -12,35 +8,33 @@ Stability : alpha Portability : portable -A generalized XML parser based on stateful arrows.-It might be sufficient to define this reader as a comonad, but there is-not a lot of use in trying.+A generalized monadic XML parser. The parser navigates through an XML+tree, always looking at a \"current element\", and carries some+additional, converter-specific state. -} module Text.Pandoc.Readers.ODT.Generic.XMLConverter ( ElementName-, XMLConverterState , XMLConverter-, FallibleXMLConverter-, runConverter'+, runConverter+, fromMaybeF+, fromFallible+, tryC , getExtraState , setExtraState , modifyExtraState-, producingExtraState-, findChild'+, getCurrentElement+, elName , filterChildrenName' , isSet' , isSetWithDefault-, elName , searchAttr , lookupAttr , lookupAttr' , lookupDefaultingAttr , findAttr'-, findAttrText' , findAttr-, findAttrText-, findAttrTextWithDefault+, findAttrWithDefault , readAttr , readAttr' , readAttrWithDefault@@ -49,271 +43,157 @@ , executeInSub , withEveryL , tryAll+, ElementMatcher , matchContent' , matchContent ) where -import Prelude hiding (Applicative(..))-import Control.Applicative hiding ( liftA, liftA2 )-import Control.Monad ( MonadPlus )-import Control.Arrow+import Control.Applicative ( Alternative(..), optional )+import Control.Monad ( filterM, foldM )+import Control.Monad.Except ( ExceptT, runExceptT, throwError, catchError )+import Control.Monad.State ( State, evalState, get, gets, put, modify ) -import Data.Bool ( bool )-import Data.Either ( rights )-import qualified Data.Map as M-import Data.Text (Text)-import Data.Default-import Data.Maybe-import qualified Data.List as L-import qualified Data.List.NonEmpty as NonEmpty-import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.Map as M+import Data.Text (Text)+import Data.Default+import Data.Maybe import qualified Text.Pandoc.XML.Light as XML -import Text.Pandoc.Readers.ODT.Arrows.State-import Text.Pandoc.Readers.ODT.Arrows.Utils-import Text.Pandoc.Readers.ODT.Generic.Namespaces-import Text.Pandoc.Readers.ODT.Generic.Utils-import Text.Pandoc.Readers.ODT.Generic.Fallible+import Text.Pandoc.Readers.ODT.Generic.Namespaces+import Text.Pandoc.Readers.ODT.Generic.Utils+import Text.Pandoc.Readers.ODT.Generic.Fallible -------------------------------------------------------------------------------- -- Basis types for readability -------------------------------------------------------------------------------- --- type ElementName = Text type AttributeName = Text type AttributeValue = Text-type TextAttributeValue = Text --- type NameSpacePrefix = Text ----type NameSpacePrefixes nsID = M.Map nsID NameSpacePrefix- ----------------------------------------------------------------------------------- Main converter state+-- Converter state -------------------------------------------------------------------------------- --- GADT so some of the NameSpaceID restrictions can be deduced-data XMLConverterState nsID extraState where- XMLConverterState :: NameSpaceID nsID- => { -- | A stack of parent elements. The top element is the current one.- -- Arguably, a real Zipper would be better. But that is an- -- optimization that can be made at a later time, e.g. when- -- replacing Text.XML.Light.- parentElements :: NonEmpty XML.Element- -- | A map from internal namespace IDs to the namespace prefixes- -- used in XML elements- , namespacePrefixes :: NameSpacePrefixes nsID- -- | A map from internal namespace IDs to namespace IRIs- -- (Only necessary for matching namespace IDs and prefixes)- , namespaceIRIs :: NameSpaceIRIs nsID- -- | A place to put "something else". This feature is used heavily- -- to keep the main code cleaner. More specifically, the main reader- -- is divided into different stages. Each stage lifts something up- -- here, which the next stage can then use. This could of course be- -- generalized to a state-tree or used for the namespace IRIs. The- -- border between states and values is an imaginary one, after all.- -- But the separation as it is seems to be enough for now.- , moreState :: extraState- }- -> XMLConverterState nsID extraState+data XMLConverterState nsID extraState = XMLConverterState+ { -- | The element that is currently being read+ currentElement :: XML.Element+ -- | A map from internal namespace IDs to the namespace prefixes+ -- used in XML elements+ , namespacePrefixes :: M.Map nsID NameSpacePrefix+ -- | A map from internal namespace IDs to namespace IRIs+ -- (Only necessary for matching namespace IDs and prefixes)+ , namespaceIRIs :: NameSpaceIRIs nsID+ -- | Converter-specific state+ , moreState :: extraState+ } --- createStartState :: (NameSpaceID nsID)- => XML.Element- -> extraState- -> XMLConverterState nsID extraState+ => XML.Element+ -> extraState+ -> XMLConverterState nsID extraState createStartState element extraState = XMLConverterState- { parentElements = element :| []+ { currentElement = element , namespacePrefixes = M.empty , namespaceIRIs = getInitialIRImap , moreState = extraState } --- | Functor over extra state-instance Functor (XMLConverterState nsID) where- fmap f ( XMLConverterState parents prefixes iRIs extraState )- = XMLConverterState parents prefixes iRIs (f extraState)-----replaceExtraState :: extraState- -> XMLConverterState nsID _x- -> XMLConverterState nsID extraState-replaceExtraState x s- = fmap (const x) s-----currentElement :: XMLConverterState nsID extraState- -> XML.Element-currentElement state = NonEmpty.head (parentElements state)---- | Replace the current position by another, modifying the extra state--- in the process-swapStack' :: XMLConverterState nsID extraState- -> NonEmpty XML.Element- -> ( XMLConverterState nsID extraState- , NonEmpty XML.Element )-swapStack' state stack- = ( state { parentElements = stack }- , parentElements state- )-----pushElement :: XML.Element- -> XMLConverterState nsID extraState- -> XMLConverterState nsID extraState-pushElement e state = state { parentElements =- NonEmpty.cons e (parentElements state) }---- | Pop the top element from the call stack, unless it is the last one.-popElement :: XMLConverterState nsID extraState- -> Maybe (XMLConverterState nsID extraState)-popElement state- | _ :| (e:es) <- parentElements state- = Just $ state { parentElements = e :| es }- | otherwise = Nothing- -------------------------------------------------------------------------------- -- Main type -------------------------------------------------------------------------------- --- It might be a good idea to pack the converters in a GADT--- Downside: data instead of type--- Upside: 'Failure' could be made a parameter as well.+-- | A converter that can read from an XML tree, may fail (with+-- 'throwError' \/ 'empty'), and carries some additional state.+-- Note that state modifications survive failure; in particular, the+-- current element must be restored explicitly where necessary+-- (see 'executeIn').+type XMLConverter nsID extraState+ = ExceptT () (State (XMLConverterState nsID extraState)) ----type XMLConverter nsID extraState input output- = ArrowState (XMLConverterState nsID extraState ) input output+-- | Run a converter on an XML element, with a given initial extra state.+runConverter :: (NameSpaceID nsID)+ => XMLConverter nsID extraState a+ -> extraState+ -> XML.Element+ -> Fallible a+runConverter converter extraState element+ = evalState (runExceptT (readNSattributes >> converter))+ (createStartState element extraState) -type FallibleXMLConverter nsID extraState input output- = XMLConverter nsID extraState input (Fallible output)+-- | Lift a 'Maybe' value into the converter, failing on 'Nothing'.+fromMaybeF :: Maybe a -> XMLConverter nsID extraState a+fromMaybeF = maybe (throwError ()) return ----runConverter :: XMLConverter nsID extraState input output- -> XMLConverterState nsID extraState- -> input- -> output-runConverter converter state input = snd $ runArrowState converter (state,input)+-- | Lift a 'Fallible' value into the converter.+fromFallible :: Fallible a -> XMLConverter nsID extraState a+fromFallible = either throwError return -runConverter' :: (NameSpaceID nsID)- => FallibleXMLConverter nsID extraState () success- -> extraState- -> XML.Element- -> Fallible success-runConverter' converter extraState element = runConverter (readNSattributes >>? converter) (createStartState element extraState) ()+-- | Run a converter, catching failure.+tryC :: XMLConverter nsID extraState a+ -> XMLConverter nsID extraState (Fallible a)+tryC converter = catchError (Right <$> converter) (return . Left) ---getCurrentElement :: XMLConverter nsID extraState x XML.Element-getCurrentElement = extractFromState currentElement+getCurrentElement :: XMLConverter nsID extraState XML.Element+getCurrentElement = gets currentElement ---getExtraState :: XMLConverter nsID extraState x extraState-getExtraState = extractFromState moreState+getExtraState :: XMLConverter nsID extraState extraState+getExtraState = gets moreState ---setExtraState :: XMLConverter nsID extraState extraState extraState-setExtraState = withState $ \state extra- -> (replaceExtraState extra state , extra)----- | Lifts a function to the extra state.-modifyExtraState :: (extraState -> extraState)- -> XMLConverter nsID extraState x x-modifyExtraState = modifyState.fmap-+setExtraState :: extraState -> XMLConverter nsID extraState ()+setExtraState x = modify $ \state -> state { moreState = x } --- | First sets the extra state to the new value. Then modifies the original--- extra state with a converter that uses the new state. Finally, the--- intermediate state is dropped and the extra state is lifted into the--- state as it was at the beginning of the function.--- As a result, exactly the extra state and nothing else is changed.--- The resulting converter even behaves like an identity converter on the--- value level. ----- (The -ing form is meant to be mnemonic in a sequence of arrows as in--- convertingExtraState () converter >>> doOtherStuff)----convertingExtraState :: extraState'- -> FallibleXMLConverter nsID extraState' extraState extraState- -> FallibleXMLConverter nsID extraState x x-convertingExtraState v a = withSubStateF setVAsExtraState modifyWithA- where- setVAsExtraState = liftAsSuccess $ extractFromState id >>^ replaceExtraState v- modifyWithA = keepingTheValue (moreState ^>> a)- >>^ spreadChoice >>?% flip replaceExtraState---- | First sets the extra state to the new value. Then produces a new--- extra state with a converter that uses the new state. Finally, the--- intermediate state is dropped and the extra state is lifted into the--- state as it was at the beginning of the function.--- As a result, exactly the extra state and nothing else is changed.--- The resulting converter even behaves like an identity converter on the--- value level.------ Equivalent to------ > \v x a -> convertingExtraState v (returnV x >>> a)------ (The -ing form is meant to be mnemonic in a sequence of arrows as in--- producingExtraState () () producer >>> doOtherStuff)----producingExtraState :: extraState'- -> a- -> FallibleXMLConverter nsID extraState' a extraState- -> FallibleXMLConverter nsID extraState x x-producingExtraState v x a = convertingExtraState v (returnV x >>> a)-+modifyExtraState :: (extraState -> extraState)+ -> XMLConverter nsID extraState ()+modifyExtraState f = modify $ \state -> state { moreState = f (moreState state) } -------------------------------------------------------------------------------- -- Work in namespaces -------------------------------------------------------------------------------- --- | Arrow version of 'getIRI'-lookupNSiri :: (NameSpaceID nsID)- => nsID- -> XMLConverter nsID extraState x (Maybe NameSpaceIRI)-lookupNSiri nsID = extractFromState- $ \state -> getIRI nsID $ namespaceIRIs state+--+lookupNSiri :: (NameSpaceID nsID)+ => nsID+ -> XMLConverter nsID extraState (Maybe NameSpaceIRI)+lookupNSiri nsID = gets $ getIRI nsID . namespaceIRIs ---lookupNSprefix :: (NameSpaceID nsID)- => nsID- -> XMLConverter nsID extraState x (Maybe NameSpacePrefix)-lookupNSprefix nsID = extractFromState- $ \state -> M.lookup nsID $ namespacePrefixes state+lookupNSprefix :: (NameSpaceID nsID)+ => nsID+ -> XMLConverter nsID extraState (Maybe NameSpacePrefix)+lookupNSprefix nsID = gets $ M.lookup nsID . namespacePrefixes -- | Extracts namespace attributes from the current element and tries to -- update the current mapping accordingly-readNSattributes :: (NameSpaceID nsID)- => FallibleXMLConverter nsID extraState x ()-readNSattributes = fromState $ \state -> maybe (state, failEmpty )- ( , succeedWith ())- (extractNSAttrs state )+readNSattributes :: (NameSpaceID nsID) => XMLConverter nsID extraState ()+readNSattributes = do+ state <- get+ maybe (throwError ()) put (extractNSAttrs state) where- extractNSAttrs :: (NameSpaceID nsID)- => XMLConverterState nsID extraState- -> Maybe (XMLConverterState nsID extraState)- extractNSAttrs startState- = L.foldl' (\state d -> state >>= addNS d)- (Just startState)- nsAttribs- where nsAttribs = mapMaybe readNSattr (XML.elAttribs element)- element = currentElement startState+ extractNSAttrs :: (NameSpaceID nsID)+ => XMLConverterState nsID extraState+ -> Maybe (XMLConverterState nsID extraState)+ extractNSAttrs startState = foldM addNS startState nsAttribs+ where nsAttribs = mapMaybe readNSattr+ (XML.elAttribs $ currentElement startState) readNSattr (XML.Attr (XML.QName name _ (Just "xmlns")) iri) = Just (name, iri) readNSattr _ = Nothing- addNS (prefix, iri) state = fmap updateState- $ getNamespaceID iri- $ namespaceIRIs state- where updateState (iris,nsID)- = state { namespaceIRIs = iris- , namespacePrefixes = M.insert nsID prefix- $ namespacePrefixes state- }+ addNS state (prefix, iri) = updateState+ <$> getNamespaceID iri (namespaceIRIs state)+ where updateState (iris, nsID)+ = state { namespaceIRIs = iris+ , namespacePrefixes = M.insert nsID prefix+ $ namespacePrefixes state+ } -------------------------------------------------------------------------------- -- Common namespace accessors@@ -321,29 +201,30 @@ -- | Given a namespace id and an element name, creates a 'XML.QName' for -- internal use-qualifyName :: (NameSpaceID nsID)- => nsID -> ElementName- -> XMLConverter nsID extraState x XML.QName-qualifyName nsID name = lookupNSiri nsID- &&& lookupNSprefix nsID- >>% XML.QName name+qualifyName :: (NameSpaceID nsID)+ => nsID -> ElementName+ -> XMLConverter nsID extraState XML.QName+qualifyName nsID name = XML.QName name <$> lookupNSiri nsID+ <*> lookupNSprefix nsID -- | Checks if a given element matches both a specified namespace id -- and a predicate-elemNameMatches :: (NameSpaceID nsID)- => nsID -> (ElementName -> Bool)- -> XMLConverter nsID extraState XML.Element Bool-elemNameMatches nsID f = keepingTheValue (lookupNSiri nsID) >>% hasMatchingName- where hasMatchingName e iri = let name = XML.elName e- in f (XML.qName name)- && XML.qURI name == iri+elemNameMatches :: (NameSpaceID nsID)+ => nsID -> (ElementName -> Bool)+ -> XML.Element+ -> XMLConverter nsID extraState Bool+elemNameMatches nsID f element = do+ iri <- lookupNSiri nsID+ let name = XML.elName element+ return $ f (XML.qName name) && XML.qURI name == iri -- | Checks if a given element matches both a specified namespace id -- and a specified element name-elemNameIs :: (NameSpaceID nsID)- => nsID -> ElementName- -> XMLConverter nsID extraState XML.Element Bool-elemNameIs nsID name = elemNameMatches nsID (== name)+elemNameIs :: (NameSpaceID nsID)+ => nsID -> ElementName+ -> XML.Element+ -> XMLConverter nsID extraState Bool+elemNameIs nsID name = elemNameMatches nsID (== name) -------------------------------------------------------------------------------- -- General content@@ -352,394 +233,245 @@ elName :: XML.Element -> ElementName elName = XML.qName . XML.elName ----elContent :: XMLConverter nsID extraState x [XML.Content]-elContent = getCurrentElement- >>^ XML.elContent- -------------------------------------------------------------------------------- -- Children -------------------------------------------------------------------------------- ------findChildren :: (NameSpaceID nsID)- => nsID -> ElementName- -> XMLConverter nsID extraState x [XML.Element]-findChildren nsID name = qualifyName nsID name- &&& getCurrentElement- >>% XML.findChildren+findChildren :: (NameSpaceID nsID)+ => nsID -> ElementName+ -> XMLConverter nsID extraState [XML.Element]+findChildren nsID name = XML.findChildren <$> qualifyName nsID name+ <*> getCurrentElement ---findChild' :: (NameSpaceID nsID)- => nsID- -> ElementName- -> XMLConverter nsID extraState x (Maybe XML.Element)-findChild' nsID name = qualifyName nsID name- &&& getCurrentElement- >>% XML.findChild+findChild' :: (NameSpaceID nsID)+ => nsID -> ElementName+ -> XMLConverter nsID extraState (Maybe XML.Element)+findChild' nsID name = XML.findChild <$> qualifyName nsID name+ <*> getCurrentElement ---findChild :: (NameSpaceID nsID)- => nsID -> ElementName- -> FallibleXMLConverter nsID extraState x XML.Element-findChild nsID name = findChild' nsID name- >>> maybeToChoice+findChild :: (NameSpaceID nsID)+ => nsID -> ElementName+ -> XMLConverter nsID extraState XML.Element+findChild nsID name = findChild' nsID name >>= fromMaybeF -filterChildrenName' :: (NameSpaceID nsID)- => nsID- -> (ElementName -> Bool)- -> XMLConverter nsID extraState x [XML.Element]-filterChildrenName' nsID f = getCurrentElement- >>> arr XML.elChildren- >>> iterateS (keepingTheValue (elemNameMatches nsID f))- >>> arr (map fst . filter snd)+--+filterChildrenName' :: (NameSpaceID nsID)+ => nsID+ -> (ElementName -> Bool)+ -> XMLConverter nsID extraState [XML.Element]+filterChildrenName' nsID f = getCurrentElement+ >>= filterM (elemNameMatches nsID f) . XML.elChildren -------------------------------------------------------------------------------- -- Attributes -------------------------------------------------------------------------------- ---isSet' :: (NameSpaceID nsID)- => nsID -> AttributeName- -> XMLConverter nsID extraState x (Maybe Bool)-isSet' nsID attrName = findAttr' nsID attrName- >>^ (>>= stringToBool')+isSet' :: (NameSpaceID nsID)+ => nsID -> AttributeName+ -> XMLConverter nsID extraState (Maybe Bool)+isSet' nsID attrName = (>>= stringToBool') <$> findAttr' nsID attrName -isSetWithDefault :: (NameSpaceID nsID)- => nsID -> AttributeName- -> Bool- -> XMLConverter nsID extraState x Bool-isSetWithDefault nsID attrName def'- = isSet' nsID attrName- >>^ fromMaybe def'+isSetWithDefault :: (NameSpaceID nsID)+ => nsID -> AttributeName+ -> Bool+ -> XMLConverter nsID extraState Bool+isSetWithDefault nsID attrName def' =+ fromMaybe def' <$> isSet' nsID attrName -- | Lookup value in a dictionary, fail if no attribute found or value -- not in dictionary-searchAttrIn :: (NameSpaceID nsID)- => nsID -> AttributeName- -> [(AttributeValue,a)]- -> FallibleXMLConverter nsID extraState x a-searchAttrIn nsID attrName dict- = findAttr nsID attrName- >>?^? maybeToChoice.(`lookup` dict )+searchAttrIn :: (NameSpaceID nsID)+ => nsID -> AttributeName+ -> [(AttributeValue,a)]+ -> XMLConverter nsID extraState a+searchAttrIn nsID attrName dict = do+ value <- findAttr nsID attrName+ fromMaybeF $ lookup value dict -- | Lookup value in a dictionary. If attribute or value not found, -- return default value-searchAttr :: (NameSpaceID nsID)- => nsID -> AttributeName- -> a- -> [(AttributeValue,a)]- -> XMLConverter nsID extraState x a-searchAttr nsID attrName defV dict- = searchAttrIn nsID attrName dict- >>> const defV ^|||^ id+searchAttr :: (NameSpaceID nsID)+ => nsID -> AttributeName+ -> a+ -> [(AttributeValue,a)]+ -> XMLConverter nsID extraState a+searchAttr nsID attrName defV dict =+ searchAttrIn nsID attrName dict <|> return defV -- | Read a 'Lookupable' attribute. Fail if no match.-lookupAttr :: (NameSpaceID nsID, Lookupable a)- => nsID -> AttributeName- -> FallibleXMLConverter nsID extraState x a-lookupAttr nsID attrName = lookupAttr' nsID attrName- >>^ maybeToChoice-+lookupAttr :: (NameSpaceID nsID, Lookupable a)+ => nsID -> AttributeName+ -> XMLConverter nsID extraState a+lookupAttr nsID attrName = lookupAttr' nsID attrName >>= fromMaybeF -- | Read a 'Lookupable' attribute. Return the result as a 'Maybe'.-lookupAttr' :: (NameSpaceID nsID, Lookupable a)- => nsID -> AttributeName- -> XMLConverter nsID extraState x (Maybe a)-lookupAttr' nsID attrName- = findAttr' nsID attrName- >>^ (>>= readLookupable)+lookupAttr' :: (NameSpaceID nsID, Lookupable a)+ => nsID -> AttributeName+ -> XMLConverter nsID extraState (Maybe a)+lookupAttr' nsID attrName =+ (>>= readLookupable) <$> findAttr' nsID attrName -- | Read a 'Lookupable' attribute with explicit default-lookupAttrWithDefault :: (NameSpaceID nsID, Lookupable a)- => nsID -> AttributeName- -> a- -> XMLConverter nsID extraState x a-lookupAttrWithDefault nsID attrName deflt- = lookupAttr' nsID attrName- >>^ fromMaybe deflt+lookupAttrWithDefault :: (NameSpaceID nsID, Lookupable a)+ => nsID -> AttributeName+ -> a+ -> XMLConverter nsID extraState a+lookupAttrWithDefault nsID attrName deflt =+ fromMaybe deflt <$> lookupAttr' nsID attrName -- | Read a 'Lookupable' attribute with implicit default-lookupDefaultingAttr :: (NameSpaceID nsID, Lookupable a, Default a)- => nsID -> AttributeName- -> XMLConverter nsID extraState x a-lookupDefaultingAttr nsID attrName- = lookupAttrWithDefault nsID attrName def---- | Return value as a (Maybe Text)-findAttr' :: (NameSpaceID nsID)- => nsID -> AttributeName- -> XMLConverter nsID extraState x (Maybe AttributeValue)-findAttr' nsID attrName = qualifyName nsID attrName- &&& getCurrentElement- >>% XML.findAttr+lookupDefaultingAttr :: (NameSpaceID nsID, Lookupable a, Default a)+ => nsID -> AttributeName+ -> XMLConverter nsID extraState a+lookupDefaultingAttr nsID attrName =+ lookupAttrWithDefault nsID attrName def -- | Return value as a (Maybe Text)-findAttrText' :: (NameSpaceID nsID)- => nsID -> AttributeName- -> XMLConverter nsID extraState x (Maybe TextAttributeValue)-findAttrText' nsID attrName- = qualifyName nsID attrName- &&& getCurrentElement- >>% XML.findAttr---- | Return value as string or fail-findAttr :: (NameSpaceID nsID)- => nsID -> AttributeName- -> FallibleXMLConverter nsID extraState x AttributeValue-findAttr nsID attrName = findAttr' nsID attrName- >>> maybeToChoice+findAttr' :: (NameSpaceID nsID)+ => nsID -> AttributeName+ -> XMLConverter nsID extraState (Maybe AttributeValue)+findAttr' nsID attrName = XML.findAttr <$> qualifyName nsID attrName+ <*> getCurrentElement --- | Return value as text or fail-findAttrText :: (NameSpaceID nsID)- => nsID -> AttributeName- -> FallibleXMLConverter nsID extraState x TextAttributeValue-findAttrText nsID attrName- = findAttr' nsID attrName- >>> maybeToChoice+-- | Return value or fail+findAttr :: (NameSpaceID nsID)+ => nsID -> AttributeName+ -> XMLConverter nsID extraState AttributeValue+findAttr nsID attrName = findAttr' nsID attrName >>= fromMaybeF --- | Return value as string or return provided default value-findAttrTextWithDefault :: (NameSpaceID nsID)- => nsID -> AttributeName- -> TextAttributeValue- -> XMLConverter nsID extraState x TextAttributeValue-findAttrTextWithDefault nsID attrName deflt- = findAttr' nsID attrName- >>^ fromMaybe deflt+-- | Return value or return provided default value+findAttrWithDefault :: (NameSpaceID nsID)+ => nsID -> AttributeName+ -> AttributeValue+ -> XMLConverter nsID extraState AttributeValue+findAttrWithDefault nsID attrName deflt =+ fromMaybe deflt <$> findAttr' nsID attrName -- | Read and return value or fail-readAttr :: (NameSpaceID nsID, Read attrValue)- => nsID -> AttributeName- -> FallibleXMLConverter nsID extraState x attrValue-readAttr nsID attrName = readAttr' nsID attrName- >>> maybeToChoice+readAttr :: (NameSpaceID nsID, Read attrValue)+ => nsID -> AttributeName+ -> XMLConverter nsID extraState attrValue+readAttr nsID attrName = readAttr' nsID attrName >>= fromMaybeF -- | Read and return value or return Nothing-readAttr' :: (NameSpaceID nsID, Read attrValue)- => nsID -> AttributeName- -> XMLConverter nsID extraState x (Maybe attrValue)-readAttr' nsID attrName = findAttr' nsID attrName- >>^ (>>= tryToRead)+readAttr' :: (NameSpaceID nsID, Read attrValue)+ => nsID -> AttributeName+ -> XMLConverter nsID extraState (Maybe attrValue)+readAttr' nsID attrName = (>>= tryToRead) <$> findAttr' nsID attrName -- | Read and return value or return provided default value-readAttrWithDefault :: (NameSpaceID nsID, Read attrValue)- => nsID -> AttributeName- -> attrValue- -> XMLConverter nsID extraState x attrValue-readAttrWithDefault nsID attrName deflt- = findAttr' nsID attrName- >>^ (>>= tryToRead)- >>^ fromMaybe deflt+readAttrWithDefault :: (NameSpaceID nsID, Read attrValue)+ => nsID -> AttributeName+ -> attrValue+ -> XMLConverter nsID extraState attrValue+readAttrWithDefault nsID attrName deflt =+ fromMaybe deflt <$> readAttr' nsID attrName -- | Read and return value or return default value from 'Default' instance-getAttr :: (NameSpaceID nsID, Read attrValue, Default attrValue)- => nsID -> AttributeName- -> XMLConverter nsID extraState x attrValue-getAttr nsID attrName = readAttrWithDefault nsID attrName def+getAttr :: (NameSpaceID nsID, Read attrValue, Default attrValue)+ => nsID -> AttributeName+ -> XMLConverter nsID extraState attrValue+getAttr nsID attrName = readAttrWithDefault nsID attrName def -------------------------------------------------------------------------------- -- Movements -------------------------------------------------------------------------------- ----jumpThere :: XMLConverter nsID extraState XML.Element XML.Element-jumpThere = withState (\state element- -> ( pushElement element state , element )- )-----swapStack :: XMLConverter nsID extraState (NonEmpty XML.Element)- (NonEmpty XML.Element)-swapStack = withState swapStack'-----jumpBack :: FallibleXMLConverter nsID extraState _x _x-jumpBack = tryModifyState (popElement >>> maybeToChoice)---- | Support function for "procedural" converters: jump to an element, execute--- a converter, jump back.--- This version is safer than 'executeThere', because it does not rely on the--- internal stack. As a result, the converter can not move around in arbitrary--- ways. The downside is of course that some of the environment is not--- accessible to the converter.-switchingTheStack :: XMLConverter nsID moreState a b- -> XMLConverter nsID moreState (a, XML.Element) b-switchingTheStack a = second ( (:| []) ^>> swapStack )- >>> first a- >>> second swapStack- >>^ fst---- | Support function for "procedural" converters: jumps to an element, executes--- a converter, jumps back.--- Make sure that the converter is well-behaved; that is it should--- return to the exact position it started from in /every possible path/ of--- execution, even if it "fails". If it does not, you may encounter--- strange bugs. If you are not sure about the behaviour or want to use--- shortcuts, you can often use 'switchingTheStack' instead.-executeThere :: FallibleXMLConverter nsID moreState a b- -> FallibleXMLConverter nsID moreState (a, XML.Element) b-executeThere a = second jumpThere- >>> fst- ^>> a- >>> jumpBack -- >>? jumpBack would not ensure the jump.- >>^ collapseEither----- | Do something in a specific element, then come back-executeIn :: XMLConverter nsID extraState XML.Element s- -> XMLConverter nsID extraState XML.Element s-executeIn a = duplicate >>> switchingTheStack a+-- | Execute a converter in a specific element, then come back.+-- The current element is restored even if the converter fails.+executeIn :: XML.Element+ -> XMLConverter nsID extraState a+ -> XMLConverter nsID extraState a+executeIn element converter = do+ oldElement <- getCurrentElement+ modify $ \state -> state { currentElement = element }+ result <- tryC converter+ modify $ \state -> state { currentElement = oldElement }+ fromFallible result --- | Do something in a sub-element, then come back-executeInSub :: (NameSpaceID nsID)- => nsID -> ElementName- -> FallibleXMLConverter nsID extraState f s- -> FallibleXMLConverter nsID extraState f s-executeInSub nsID name a = keepingTheValue- (findChild nsID name)- >>> ignoringState liftFailure- >>? switchingTheStack a- where liftFailure (_, Left f) = Left f- liftFailure (x, Right e) = Right (x, e)+-- | Execute a converter in a sub-element of the current element,+-- then come back. Fails if there is no such sub-element.+executeInSub :: (NameSpaceID nsID)+ => nsID -> ElementName+ -> XMLConverter nsID extraState a+ -> XMLConverter nsID extraState a+executeInSub nsID name converter = do+ child <- findChild nsID name+ executeIn child converter -------------------------------------------------------------------------------- -- Iterating over children -------------------------------------------------------------------------------- --- Helper converter to prepare different types of iterations.--- It lifts the children (of a certain type) of the current element--- into the value level and pairs each one with the current input value.-prepareIteration :: (NameSpaceID nsID)- => nsID -> ElementName- -> XMLConverter nsID extraState b [(b, XML.Element)]-prepareIteration nsID name = keepingTheValue- (findChildren nsID name)- >>% distributeValue-----withEveryL :: (NameSpaceID nsID)- => nsID -> ElementName- -> FallibleXMLConverter nsID extraState a b- -> FallibleXMLConverter nsID extraState a [b]-withEveryL = withEvery- -- | Applies a converter to every child element of a specific type.--- Collects results in a 'MonadPlus'. -- Fails completely if any conversion fails.-withEvery :: (NameSpaceID nsID, MonadPlus m)- => nsID -> ElementName- -> FallibleXMLConverter nsID extraState a b- -> FallibleXMLConverter nsID extraState a (m b)-withEvery nsID name a = prepareIteration nsID name- >>> iterateS' (switchingTheStack a)+withEveryL :: (NameSpaceID nsID)+ => nsID -> ElementName+ -> XMLConverter nsID extraState a+ -> XMLConverter nsID extraState [a]+withEveryL nsID name converter = do+ children <- findChildren nsID name+ mapM (`executeIn` converter) children -- | Applies a converter to every child element of a specific type. -- Collects all successful results in a list.-tryAll :: (NameSpaceID nsID)- => nsID -> ElementName- -> FallibleXMLConverter nsID extraState b a- -> XMLConverter nsID extraState b [a]-tryAll nsID name a = prepareIteration nsID name- >>> iterateS (switchingTheStack a)- >>^ rights+tryAll :: (NameSpaceID nsID)+ => nsID -> ElementName+ -> XMLConverter nsID extraState a+ -> XMLConverter nsID extraState [a]+tryAll nsID name converter = do+ children <- findChildren nsID name+ catMaybes <$> mapM (\child -> optional (executeIn child converter)) children -------------------------------------------------------------------------------- -- Matching children -------------------------------------------------------------------------------- -type IdXMLConverter nsID moreState x- = XMLConverter nsID moreState x x--type MaybeCConverter nsID moreState x- = Maybe (IdXMLConverter nsID moreState (x, XML.Content))---- Chainable converter that helps deciding which converter to actually use.-type ContentMatchConverter nsID extraState x- = IdXMLConverter nsID- extraState- (MaybeCConverter nsID extraState x, XML.Content)---- Helper function: The @c@ is actually a converter that is to be selected by--- matching XML content to the first two parameters.--- The fold used to match elements however is very simple, so to use it,--- this function wraps the converter in another converter that unifies--- the accumulator. Think of a lot of converters with the resulting type--- chained together. The accumulator not only transports the element--- unchanged to the next matcher, it also does the actual selecting by--- combining the intermediate results with '(<|>)'.-makeMatcherC :: (NameSpaceID nsID)- => nsID -> ElementName- -> FallibleXMLConverter nsID extraState a a- -> ContentMatchConverter nsID extraState a-makeMatcherC nsID name c = ( second ( contentToElem- >>> returnV Nothing- ||| ( elemNameIs nsID name- >>^ bool Nothing (Just cWithJump)- )- )- >>% (<|>)- ) &&&^ snd- where cWithJump = ( fst- ^&&& ( second contentToElem- >>> spreadChoice- ^>>? executeThere c- )- >>% recover)- &&&^ snd- contentToElem :: FallibleXMLConverter nsID extraState XML.Content XML.Element- contentToElem = arr $ \case- XML.Elem e' -> succeedWith e'- _ -> failEmpty---- Creates and chains a bunch of matchers-prepareMatchersC :: (NameSpaceID nsID)- => [(nsID, ElementName, FallibleXMLConverter nsID extraState x x)]- -> ContentMatchConverter nsID extraState x---prepareMatchersC = foldSs . (map $ uncurry3 makeMatcherC)-prepareMatchersC = reverseComposition . map (uncurry3 makeMatcherC)+-- | A converter for a child element with a specific name in a specific+-- namespace. The converter produces that element's contribution to the+-- overall result.+type ElementMatcher nsID extraState a+ = (nsID, ElementName, XMLConverter nsID extraState a) --- | Takes a list of element-data - converter groups and--- * Finds all content of the current element--- * Matches each group to each piece of content in order--- (at most one group per piece of content)--- * Filters non-matched content--- * Chains all found converters in content-order--- * Applies the chain to the input element-matchContent' :: (NameSpaceID nsID)- => [(nsID, ElementName, FallibleXMLConverter nsID extraState a a)]- -> XMLConverter nsID extraState a a-matchContent' lookups = matchContent lookups (arr fst)+-- | Like 'matchContent', but ignores non-matching content.+matchContent' :: (NameSpaceID nsID, Monoid a)+ => [ElementMatcher nsID extraState a]+ -> XMLConverter nsID extraState a+matchContent' lookups = matchContent lookups (\_ -> return mempty) --- | Takes a list of element-data - converter groups and--- * Finds all content of the current element--- * Matches each group to each piece of content in order--- (at most one group per piece of content)--- * Adds a default converter for all non-matched content--- * Chains all found converters in content-order--- * Applies the chain to the input element-matchContent :: (NameSpaceID nsID)- => [(nsID, ElementName, FallibleXMLConverter nsID extraState a a)]- -> XMLConverter nsID extraState (a,XML.Content) a- -> XMLConverter nsID extraState a a-matchContent lookups fallback- = let matcher = prepareMatchersC lookups- in keepingTheValue (- elContent- >>> map (Nothing,)- ^>> iterateSL matcher- >>^ map swallowOrFallback- -- >>> foldSs- >>> reverseComposition- )- >>> swap- ^>> app+-- | Takes a list of element matchers and a fallback converter, and+-- converts the content of the current element in order: for each child+-- element, the first matcher with a matching name (if any) is applied+-- in that element; all other content is passed to the fallback+-- converter. The results are combined with 'mappend'.+-- If a matched converter fails, the corresponding element contributes+-- nothing to the result.+matchContent :: (NameSpaceID nsID, Monoid a)+ => [ElementMatcher nsID extraState a]+ -> (XML.Content -> XMLConverter nsID extraState a)+ -> XMLConverter nsID extraState a+matchContent lookups fallback = do+ contents <- XML.elContent <$> getCurrentElement+ mconcat <$> mapM matchOne contents where- -- let the converter swallow the content and drop the content- -- in the return value- swallowOrFallback (Just converter,content) = (,content) ^>> converter >>^ fst- swallowOrFallback (Nothing ,content) = (,content) ^>> fallback+ matchOne content@(XML.Elem element) = do+ mConverter <- findConverter element lookups+ case mConverter of+ Just converter -> executeIn element converter <|> return mempty+ Nothing -> fallback content+ matchOne content = fallback content + findConverter _ [] = return Nothing+ findConverter element ((nsID, name, converter):rest) = do+ matches <- elemNameIs nsID name element+ if matches+ then return $ Just converter+ else findConverter element rest+ -------------------------------------------------------------------------------- -- Internals --------------------------------------------------------------------------------@@ -750,32 +482,3 @@ | otherwise = Nothing where trueValues = ["true" ,"on" ,"1"] falseValues = ["false","off","0"]---distributeValue :: a -> [b] -> [(a,b)]-distributeValue = map.(,)------------------------------------------------------------------------------------{--NOTES-It might be a good idea to refactor the namespace stuff.-E.g.: if a namespace constructor took a string as a parameter, things like-> a ?>/< (NsText,"body")-would be nicer.-Together with a rename and some trickery, something like-> |< NsText "body" >< NsText "p" ?> a </> </>|-might even be possible.--Some day, XML.Light should be replaced by something better.-While doing that, it might be useful to replace String as the type of element-names with something else, too. (Of course with OverloadedStrings).-While doing that, maybe the types can be created in a way that something like-> NsText:"body"-could be used. Overloading (:) does not sounds like the best idea, but if the-element name type was a list, this might be possible.-Of course that would be a bit hackish, so the "right" way would probably be-something like-> InNS NsText "body"-but isn't that a bit boring? ;)--}
@@ -1,5 +1,3 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE Arrows #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE OverloadedStrings #-}@@ -39,9 +37,7 @@ , readStylesAt ) where -import Prelude hiding (Applicative(..))-import Control.Applicative hiding (liftA, liftA2, liftA3)-import Control.Arrow+import Control.Applicative ((<|>), optional) import Data.Default import qualified Data.Foldable as F@@ -56,18 +52,15 @@ import Text.Pandoc.Shared (safeRead, tshow) -import Text.Pandoc.Readers.ODT.Arrows.Utils- import Text.Pandoc.Readers.ODT.Generic.Fallible import qualified Text.Pandoc.Readers.ODT.Generic.SetMap as SM import Text.Pandoc.Readers.ODT.Generic.Utils import Text.Pandoc.Readers.ODT.Generic.XMLConverter -import Text.Pandoc.Readers.ODT.Base import Text.Pandoc.Readers.ODT.Namespaces readStylesAt :: XML.Element -> Fallible Styles-readStylesAt e = runConverter' readAllStyles mempty e+readStylesAt e = runConverter readAllStyles mempty e -------------------------------------------------------------------------------- -- Reader for font declarations and font pitches@@ -99,41 +92,29 @@ type FontPitches = M.Map FontFaceName FontPitch -- To get there, the fonts have to be read and the pitches extracted.--- But the resulting map are only needed at one later place, so it should not be--- transported on the value level, especially as we already use a state arrow.--- So instead, the resulting map is lifted into the state of the reader.--- (An alternative might be ImplicitParams, but again, we already have a state.)+-- But the resulting map is only needed at one later place, so it should not be+-- transported on the value level. Instead, it is lifted into the state of the+-- reader. ----- So the main style readers will have the types-type StyleReader a b = XMLReader FontPitches a b--- and-type StyleReaderSafe a b = XMLReaderSafe FontPitches a b--- respectively.+-- So the main style readers will have the type+type StyleReader a = XMLConverter Namespace FontPitches a ----- But before we can work with these, we need to define the reader that reads--- the fonts:+-- But before we can work with this, we need to define the reader that reads+-- the fonts. It is polymorphic in the extra state, since the pitches are not+-- yet available while it runs. -- | A reader for font pitches-fontPitchReader :: XMLReader s a FontPitches-fontPitchReader = executeInSub NsOffice "font-face-decls" (- withEveryL NsStyle "font-face" (liftAsSuccess (- findAttr' NsStyle "name"- &&&- lookupDefaultingAttr NsStyle "font-pitch"- ))- >>?^ ( M.fromList . L.foldl' accumLegalPitches [] )- ) `ifFailedDo` returnV (Right M.empty)- where accumLegalPitches ls (Nothing,_) = ls- accumLegalPitches ls (Just n,p) = (n,p):ls----- | A wrapper around the font pitch reader that lifts the result into the--- state.-readFontPitches :: StyleReader a a-readFontPitches = producingExtraState () () fontPitchReader-+fontPitchReader :: XMLConverter Namespace s FontPitches+fontPitchReader =+ executeInSub NsOffice "font-face-decls" (do+ fonts <- withEveryL NsStyle "font-face" $ do+ name <- findAttr' NsStyle "name"+ pitch <- lookupDefaultingAttr NsStyle "font-pitch"+ return (name, pitch)+ return $ M.fromList [ (n, p) | (Just n, p) <- fonts ])+ <|> return M.empty --- | Looking up a pitch in the state of the arrow.+-- | Looking up a pitch in the extra state. -- -- The function does the following: -- * Look for the font pitch in an attribute.@@ -141,15 +122,12 @@ -- and use the pitch from there. -- * Return the result in a Maybe ---findPitch :: XMLReaderSafe FontPitches a (Maybe FontPitch)-findPitch = ( lookupAttr NsStyle "font-pitch"- `ifFailedDo` findAttr NsStyle "font-name"- >>? ( keepingTheValue getExtraState- >>% M.lookup- >>^ maybeToChoice- )- )- >>> choiceToMaybe+findPitch :: StyleReader (Maybe FontPitch)+findPitch = optional+ $ lookupAttr NsStyle "font-pitch"+ <|> (do fontName <- findAttr NsStyle "font-name"+ pitches <- getExtraState+ fromMaybeF $ M.lookup fontName pitches) -------------------------------------------------------------------------------- -- Definitions of main data@@ -416,84 +394,77 @@ -------------------------------------------------------------------------------- ---readAllStyles :: StyleReader a Styles-readAllStyles = ( readFontPitches- >>?! ( readAutomaticStyles- &&& readStyles ))- >>?%? chooseMax+readAllStyles :: StyleReader Styles+readAllStyles = do+ fontPitchReader >>= setExtraState+ autoStyles <- tryC readAutomaticStyles+ styles <- tryC readStyles+ fromFallible $ chooseMax autoStyles styles -- all top elements are always on the same hierarchy level ---readStyles :: StyleReader a Styles-readStyles = executeInSub NsOffice "styles" $ liftAsSuccess- $ liftA3 Styles- ( tryAll NsStyle "style" readStyle >>^ M.fromList )- ( tryAll NsText "list-style" readListStyle >>^ M.fromList )- ( tryAll NsStyle "default-style" readDefaultStyle >>^ M.fromList )+readStyles :: StyleReader Styles+readStyles = executeInSub NsOffice "styles" $+ Styles <$> (M.fromList <$> tryAll NsStyle "style" readStyle )+ <*> (M.fromList <$> tryAll NsText "list-style" readListStyle )+ <*> (M.fromList <$> tryAll NsStyle "default-style" readDefaultStyle) ---readAutomaticStyles :: StyleReader a Styles-readAutomaticStyles = executeInSub NsOffice "automatic-styles" $ liftAsSuccess- $ liftA3 Styles- ( tryAll NsStyle "style" readStyle >>^ M.fromList )- ( tryAll NsText "list-style" readListStyle >>^ M.fromList )- ( returnV M.empty )+readAutomaticStyles :: StyleReader Styles+readAutomaticStyles = executeInSub NsOffice "automatic-styles" $+ Styles <$> (M.fromList <$> tryAll NsStyle "style" readStyle )+ <*> (M.fromList <$> tryAll NsText "list-style" readListStyle )+ <*> pure M.empty ---readDefaultStyle :: StyleReader a (StyleFamily, StyleProperties)-readDefaultStyle = lookupAttr NsStyle "family"- >>?! keepingTheValue readStyleProperties+readDefaultStyle :: StyleReader (StyleFamily, StyleProperties)+readDefaultStyle = (,) <$> lookupAttr NsStyle "family"+ <*> readStyleProperties ---readStyle :: StyleReader a (StyleName,Style)-readStyle = findAttr NsStyle "name"- >>?! keepingTheValue- ( liftA4 Style- ( lookupAttr' NsStyle "family" )- ( findAttr' NsStyle "parent-style-name" )- ( findAttr' NsStyle "list-style-name" )- readStyleProperties- )+readStyle :: StyleReader (StyleName, Style)+readStyle = do+ name <- findAttr NsStyle "name"+ style <- Style <$> lookupAttr' NsStyle "family"+ <*> findAttr' NsStyle "parent-style-name"+ <*> findAttr' NsStyle "list-style-name"+ <*> readStyleProperties+ return (name, style) ---readStyleProperties :: StyleReaderSafe a StyleProperties-readStyleProperties = liftA2 SProps- ( readTextProperties >>> choiceToMaybe )- ( readParaProperties >>> choiceToMaybe )+readStyleProperties :: StyleReader StyleProperties+readStyleProperties = SProps <$> optional readTextProperties+ <*> optional readParaProperties ---readTextProperties :: StyleReader a TextProperties+readTextProperties :: StyleReader TextProperties readTextProperties =- executeInSub NsStyle "text-properties" $ liftAsSuccess- ( liftA6 PropT- ( searchAttr NsXSL_FO "font-style" False isFontEmphasised )- ( searchAttr NsXSL_FO "font-weight" False isFontBold )- findPitch- ( getAttr NsStyle "text-position" )- readUnderlineMode- readStrikeThroughMode- )+ executeInSub NsStyle "text-properties" $+ PropT <$> searchAttr NsXSL_FO "font-style" False isFontEmphasised+ <*> searchAttr NsXSL_FO "font-weight" False isFontBold+ <*> findPitch+ <*> getAttr NsStyle "text-position"+ <*> readUnderlineMode+ <*> readStrikeThroughMode where isFontEmphasised = [("normal",False),("italic",True),("oblique",True)] isFontBold = ("normal",False):("bold",True) :map ((,True) . tshow) ([100,200..900]::[Int]) -readUnderlineMode :: StyleReaderSafe a (Maybe UnderlineMode)+readUnderlineMode :: StyleReader (Maybe UnderlineMode) readUnderlineMode = readLineMode "text-underline-mode" "text-underline-style" -readStrikeThroughMode :: StyleReaderSafe a (Maybe UnderlineMode)+readStrikeThroughMode :: StyleReader (Maybe UnderlineMode) readStrikeThroughMode = readLineMode "text-line-through-mode" "text-line-through-style" -readLineMode :: Text -> Text -> StyleReaderSafe a (Maybe UnderlineMode)-readLineMode modeAttr styleAttr = proc x -> do- isUL <- searchAttr NsStyle styleAttr False isLinePresent -< x- mode <- lookupAttr' NsStyle modeAttr -< x- if isUL- then case mode of- Just m -> returnA -< Just m- Nothing -> returnA -< Just UnderlineModeNormal- else returnA -< Nothing+readLineMode :: Text -> Text -> StyleReader (Maybe UnderlineMode)+readLineMode modeAttr styleAttr = do+ isUL <- searchAttr NsStyle styleAttr False isLinePresent+ mode <- lookupAttr' NsStyle modeAttr+ return $ if isUL+ then mode <|> Just UnderlineModeNormal+ else Nothing where isLinePresent = ("none",False) : map (,True) [ "dash" , "dot-dash" , "dot-dot-dash" , "dotted"@@ -501,20 +472,16 @@ ] ---readParaProperties :: StyleReader a ParaProperties+readParaProperties :: StyleReader ParaProperties readParaProperties =- executeInSub NsStyle "paragraph-properties" $ liftAsSuccess- ( liftA3 PropP- ( liftA2 readNumbering- ( isSet' NsText "number-lines" )- ( readAttr' NsText "line-number" )- )- ( liftA2 readIndentation- ( isSetWithDefault NsStyle "auto-text-indent" False )- ( getAttr NsXSL_FO "text-indent" )- )- ( getAttr NsXSL_FO "margin-left" )- )+ executeInSub NsStyle "paragraph-properties" $+ PropP <$> ( readNumbering+ <$> isSet' NsText "number-lines"+ <*> readAttr' NsText "line-number" )+ <*> ( readIndentation+ <$> isSetWithDefault NsStyle "auto-text-indent" False+ <*> getAttr NsXSL_FO "text-indent" )+ <*> getAttr NsXSL_FO "margin-left" where readNumbering (Just True) (Just n) = NumberingRestart n readNumbering (Just True) _ = NumberingKeep readNumbering _ _ = NumberingNone@@ -527,39 +494,37 @@ ---- ---readListStyle :: StyleReader a (StyleName, ListStyle)-readListStyle =- findAttr NsStyle "name"- >>?! keepingTheValue- ( liftA ListStyle- $ liftA3 SM.union3- ( readListLevelStyles NsText "list-level-style-number" LltNumbered )- ( readListLevelStyles NsText "list-level-style-bullet" LltBullet )- ( readListLevelStyles NsText "list-level-style-image" LltImage ) >>^ M.mapMaybe chooseMostSpecificListLevelStyle- )+readListStyle :: StyleReader (StyleName, ListStyle)+readListStyle = do+ name <- findAttr NsStyle "name"+ styles <- SM.union3+ <$> readListLevelStyles NsText "list-level-style-number" LltNumbered+ <*> readListLevelStyles NsText "list-level-style-bullet" LltBullet+ <*> readListLevelStyles NsText "list-level-style-image" LltImage+ return ( name+ , ListStyle $ M.mapMaybe chooseMostSpecificListLevelStyle styles )+ -- readListLevelStyles :: Namespace -> ElementName -> ListLevelType- -> StyleReaderSafe a (SM.SetMap Int ListLevelStyle)+ -> StyleReader (SM.SetMap Int ListLevelStyle) readListLevelStyles namespace elementName levelType =- tryAll namespace elementName (readListLevelStyle levelType)- >>^ SM.fromList+ SM.fromList <$> tryAll namespace elementName (readListLevelStyle levelType) ---readListLevelStyle :: ListLevelType -> StyleReader a (Int, ListLevelStyle)-readListLevelStyle levelType = readAttr NsText "level"- >>?! keepingTheValue- ( liftA5 toListLevelStyle- ( returnV levelType )- ( findAttr' NsStyle "num-prefix" )- ( findAttr' NsStyle "num-suffix" )- ( getAttr NsStyle "num-format" )- ( findAttrText' NsText "start-value" )- )+readListLevelStyle :: ListLevelType -> StyleReader (Int, ListLevelStyle)+readListLevelStyle levelType = do+ level <- readAttr NsText "level"+ style <- toListLevelStyle+ <$> findAttr' NsStyle "num-prefix"+ <*> findAttr' NsStyle "num-suffix"+ <*> getAttr NsStyle "num-format"+ <*> findAttr' NsText "start-value"+ return (level, style) where- toListLevelStyle _ p s LinfNone b = ListLevelStyle LltBullet p s LinfNone (startValue b)- toListLevelStyle _ p s f@(LinfString _) b = ListLevelStyle LltBullet p s f (startValue b)- toListLevelStyle t p s f b = ListLevelStyle t p s f (startValue b)+ toListLevelStyle p s LinfNone b = ListLevelStyle LltBullet p s LinfNone (startValue b)+ toListLevelStyle p s f@(LinfString _) b = ListLevelStyle LltBullet p s f (startValue b)+ toListLevelStyle p s f b = ListLevelStyle levelType p s f (startValue b) startValue mbx = fromMaybe 1 (mbx >>= safeRead) --@@ -605,7 +570,7 @@ parents :: Style -> Styles -> [Style] parents style styles = L.unfoldr findNextParent style -- Ha! where findNextParent Style{..}- = fmap duplicate $ (`lookupStyle` styles) =<< styleParentName+ = fmap (\p -> (p, p)) $ (`lookupStyle` styles) =<< styleParentName -- | Looks up the style family of the current style. Normally, every style -- should have one. But if not, all parents are searched.
@@ -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
@@ -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 } --
@@ -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 ()
@@ -36,6 +36,7 @@ import Control.Monad.Trans (lift) import Data.Char (isAlphaNum, isSpace) import qualified Data.Map as M+import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T @@ -124,7 +125,7 @@ str :: PandocMonad m => OrgParser m (F Inlines) str = return . B.str <$>- ( many1Char (noneOf $ specialChars ++ "\n\r ") >>= updatePositions' )+ ( takeWhile1P (`notElem` (specialChars ++ "\n\r ")) >>= updatePositions' ) <* updateLastStrPos where updatePositions' str' = str' <$@@ -222,7 +223,7 @@ orgCiteKey :: PandocMonad m => OrgParser m Text orgCiteKey = do char '@'- T.pack <$> many1 (satisfy orgCiteKeyChar)+ takeWhile1P orgCiteKeyChar orgCiteKeyChar :: Char -> Bool orgCiteKeyChar c =@@ -409,7 +410,7 @@ inlineNote :: PandocMonad m => OrgParser m (F Inlines) inlineNote = try $ do string "[fn:"- ref <- manyChar alphaNum+ ref <- takeWhileP (\c -> isAlphaNum c || c == '-' || c == '_') char ':' note <- fmap B.para . trimInlinesF . mconcat <$> many1Till inline (char ']') unless (T.null ref) $@@ -501,7 +502,7 @@ internalLink :: Text -> Inlines -> F Inlines internalLink link title = do ids <- asksF orgStateAnchorIds- if link `elem` ids+ if link `Set.member` ids then return $ B.link ("#" <> link) "" title else let attr' = ("", ["spurious-link"] , [("target", link)]) in return $ B.spanWith attr' (B.emph title)@@ -547,7 +548,7 @@ orgInlineParamValue = try $ skipSpaces *> notFollowedBy (char ':')- *> many1Char (noneOf "\t\n\r ]")+ *> takeWhile1P (`notElem` ("\t\n\r ]" :: [Char])) <* skipSpaces @@ -801,21 +802,26 @@ -- | Read a sub- or superscript expression subOrSuperExpr :: PandocMonad m => OrgParser m (F Inlines)-subOrSuperExpr = try $- simpleSubOrSuperText <|>- (choice [ charsInBalanced '{' '}' (T.singleton <$> noneOf "\n\r")- , enclosing ('(', ')') <$> charsInBalanced '(' ')' (T.singleton <$> noneOf "\n\r")- ] >>= parseFromString (mconcat <$> many inline))- where enclosing (left, right) s = T.cons left $ T.snoc s right+subOrSuperExpr = try $ do+ subSupOption <- getExportSetting exportSubSuperscripts+ case subSupOption of+ SubSupNone -> mzero+ SubSupBraced -> bracedText+ SubSupAll -> simpleSubOrSuperText <|> bracedText <|> parenText+ where+ bracedText = charsInBalanced '{' '}' (T.singleton <$> noneOf "\n\r")+ >>= parseFromString (mconcat <$> many inline)+ parenText = (enclosing ('(', ')') <$>+ charsInBalanced '(' ')' (T.singleton <$> noneOf "\n\r"))+ >>= parseFromString (mconcat <$> many inline)+ enclosing (left, right) s = T.cons left $ T.snoc s right simpleSubOrSuperText :: PandocMonad m => OrgParser m (F Inlines)-simpleSubOrSuperText = try $ do- state <- getState- guard . exportSubSuperscripts . orgStateExportSettings $ state+simpleSubOrSuperText = try $ return . B.str <$> choice [ textStr "*" , mappend <$> option "" (T.singleton <$> oneOf "+-")- <*> many1Char alphaNum+ <*> takeWhile1P isAlphaNum ] inlineLaTeX :: PandocMonad m => OrgParser m (F Inlines)@@ -891,7 +897,7 @@ recursionDepth <- orgStateMacroDepth <$> getState guard $ recursionDepth < 15 string "{{{"- name <- manyChar alphaNum+ name <- takeWhileP isAlphaNum args <- ([] <$ string "}}}") <|> char '(' *> argument `sepBy` char ',' <* eoa expander <- lookupMacro name <$> getState@@ -922,7 +928,7 @@ guard =<< getExportSetting exportSpecialStrings choice [orgDash, orgEllipses, shyHyphen] where- shyHyphen = pure <$> (B.str "\173" <$ string "\\-") <* updatePositions '-'+ shyHyphen = pure <$> (B.str "\173" <$ try (string "\\-")) <* updatePositions '-' orgDash = pure <$> dash <* updatePositions '-' orgEllipses = pure <$> ellipses <* updatePositions '.'
@@ -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")
@@ -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
@@ -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) }
@@ -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
@@ -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
@@ -18,10 +18,11 @@ import Control.Monad (forM_, guard, liftM, mplus, mzero, when, unless, void) import Control.Monad.Except (throwError) import Control.Monad.Identity (Identity (..))-import Data.Char (isHexDigit, isSpace, toUpper, isAlphaNum, generalCategory,+import Data.Char (isDigit, isHexDigit, isSpace, toUpper, isAlphaNum,+ generalCategory, GeneralCategory(OpenPunctuation, InitialQuote, FinalQuote, DashPunctuation, OtherSymbol))-import Data.List (deleteFirstsBy, elemIndex, partition, sort, transpose)+import Data.List (elemIndex, partition, sort, transpose) import qualified Data.Map as M import Data.Maybe (fromMaybe, maybeToList, isJust, isNothing, catMaybes) import Data.Sequence (ViewR (..), viewr)@@ -75,8 +76,35 @@ underlineChars = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" -- treat these as potentially non-text when parsing inline:-specialChars :: [Char]-specialChars = "\\`|*_<>$:/[]{}()-.\"'\8216\8217\8220\8221"+-- (equivalent to the character class "\\`|*_<>$:/[]{}()-.\"'\8216\8217\8220\8221")+isSpecialChar :: Char -> Bool+isSpecialChar c =+ case c of+ '\\' -> True+ '`' -> True+ '|' -> True+ '*' -> True+ '_' -> True+ '<' -> True+ '>' -> True+ '$' -> True+ ':' -> True+ '/' -> True+ '[' -> True+ ']' -> True+ '{' -> True+ '}' -> True+ '(' -> True+ ')' -> True+ '-' -> True+ '.' -> True+ '"' -> True+ '\'' -> True+ '\8216' -> True+ '\8217' -> True+ '\8220' -> True+ '\8221' -> True+ _ -> False -- -- parsing documents@@ -121,8 +149,10 @@ metaFromDefList ds meta = adjustAuthors $ foldr f meta ds where f (k,v) = case v of- [[Plain ils]] -> setMeta (T.toLower (stringify k)) $ MetaInlines ils- _ -> setMeta (T.toLower (stringify k)) $ mconcat $ map fromList v+ [[Plain ils]] -> setMeta (T.toLower (stringifyInlines k)) $+ MetaInlines ils+ _ -> setMeta (T.toLower (stringifyInlines k)) $+ mconcat $ map fromList v adjustAuthors (Meta metamap) = Meta $ M.adjust splitAuthors "author" $ M.mapKeys (\k -> if k == "authors"@@ -163,9 +193,15 @@ let (blocks', meta') = if standalone then titleTransform (blocks, meta) else (blocks, meta)- let reversedNotes = stateNotes state- updateState $ \s -> s { stateNotes = reverse reversedNotes }- doc <- walkM resolveReferences =<<+ let notes = reverse $ stateNotes state+ -- Named notes are never removed, so we can look them up in a Map;+ -- auto-numbered notes are consumed in order of occurrence, so we+ -- keep them in a list (in stateNotes):+ let isAutoNote (r, _) = r == "*" || r == "#"+ let namedNotes = M.fromList $ reverse $ filter (not . isAutoNote) notes+ -- reverse, so that the first occurrence of a label wins+ updateState $ \s -> s { stateNotes = filter isAutoNote notes }+ doc <- walkM (resolveReferences namedNotes) =<< walkM resolveBlockSubstitutions (Pandoc meta' (blocks' ++ refBlock)) reportLogMessages@@ -187,22 +223,27 @@ bls -> return $ Div nullAttr bls resolveBlockSubstitutions x = return x -resolveReferences :: PandocMonad m => Inline -> RSTParser m Inline-resolveReferences = resolveReferences' Set.empty+resolveReferences :: PandocMonad m+ => M.Map Text Text -- ^ named notes+ -> Inline -> RSTParser m Inline+resolveReferences namedNotes = resolveReferences' namedNotes Set.empty -resolveReferences' :: PandocMonad m => Set.Set Key -> Inline -> RSTParser m Inline-resolveReferences' seen x@(Link _ ils (s,_))+resolveReferences' :: PandocMonad m+ => M.Map Text Text -> Set.Set Key -> Inline+ -> RSTParser m Inline+resolveReferences' namedNotes seen x@(Link _ ils (s,_)) | Just ref <- T.stripPrefix "##REF##" s = do let isAnonKey (Key (T.uncons -> Just ('_',_))) = True isAnonKey _ = False state <- getState let keyTable = stateKeys state- let anonKeys = sort $ filter isAnonKey $ M.keys keyTable key <- if ref == "_" -- anonymous key then- case anonKeys of- [] -> mzero -- TODO log?- (k:_) -> return k+ -- anonymous keys are named _0000, _0001, ... so the+ -- next one to use is the smallest key >= "_":+ case M.lookupGE (Key "_") keyTable of+ Just (k, _) | isAnonKey k -> return k+ _ -> mzero -- TODO log? else return $ toKey ref if key `Set.member` seen then do@@ -214,27 +255,31 @@ ((src,tit), attr) <- lookupKey [] key when (isAnonKey key) $ updateState $ \st -> st{ stateKeys = M.delete key keyTable }- resolveReferences' (Set.insert key seen) (Link attr ils (src, tit))+ resolveReferences' namedNotes (Set.insert key seen)+ (Link attr ils (src, tit)) | Just ref <- T.stripPrefix "##NOTE##" s = do state <- getState- let notes = stateNotes state- case lookup ref notes of+ let autoNotes = stateNotes state+ let mbnote = if ref == "*" || ref == "#" -- auto-numbered+ -- consume the note, so the next auto-numbered+ -- note doesn't get the same contents:+ then case break ((== ref) . fst) autoNotes of+ (xs, (_, raw) : ys) -> Just (raw, xs ++ ys)+ _ -> Nothing+ else (\raw -> (raw, autoNotes)) <$>+ M.lookup ref namedNotes+ case mbnote of Nothing -> do pos <- getPosition logMessage $ ReferenceNotFound ref pos return x- Just raw -> do+ Just (raw, newnotes) -> do -- We temporarily empty the note list while parsing the note, -- so that we don't get infinite loops with notes inside notes... -- Note references inside other notes are allowed in reST, but -- not yet in this implementation. updateState $ \st -> st{ stateNotes = [] } contents <- parseFromString' parseBlocks raw- let newnotes = if ref == "*" || ref == "#" -- auto-numbered- -- delete the note so the next auto-numbered note- -- doesn't get the same contents:- then deleteFirstsBy (==) notes [(ref,raw)]- else notes updateState $ \st -> st{ stateNotes = newnotes } return $ Note (B.toList contents) | Just ref <- T.stripPrefix "##SUBST##" s = do@@ -255,9 +300,9 @@ [Para [t]] -> return t [Para xs] -> return $ Span nullAttr xs bls -> return $ Span nullAttr $ blocksToInlines bls- resolveReferences' (Set.insert key seen) resolved+ resolveReferences' namedNotes (Set.insert key seen) resolved | otherwise = return x-resolveReferences' _ x = return x+resolveReferences' _ _ x = return x parseCitation :: PandocMonad m => (Text, Text) -> RSTParser m (Inlines, [Blocks])@@ -433,7 +478,7 @@ Nothing -> (headerTable ++ [DoubleHeader c], length headerTable + 1) setState (state { stateHeaderTable = headerTable' }) attr@(ident,_,_) <- registerHeader nullAttr txt- let key = toKey (stringify txt)+ let key = toKey (stringifyInlines txt) updateState $ \s -> s { stateKeys = M.insert key (("#" <> ident,""), nullAttr) $ stateKeys s } return $ B.headerWith attr level txt@@ -465,7 +510,7 @@ Nothing -> (headerTable ++ [SingleHeader c], length headerTable + 1) setState (state { stateHeaderTable = headerTable' }) attr@(ident,_,_) <- registerHeader nullAttr txt- let key = toKey (stringify txt)+ let key = toKey (stringifyInlines txt) updateState $ \s -> s { stateKeys = M.insert key (("#" <> ident,""), nullAttr) $ stateKeys s } return $ B.headerWith attr level txt@@ -473,7 +518,14 @@ singleHeader' :: PandocMonad m => RSTParser m (Inlines, Char) singleHeader' = try $ do notFollowedBy' whitespace- lookAhead $ anyLine >> oneOf underlineChars+ -- check that the next line is a full underline before committing+ -- to parsing the header text (otherwise we'd parse the first line+ -- of many paragraphs twice):+ lookAhead $ do+ anyLine+ c <- oneOf underlineChars+ skipMany (char c)+ blankline txt <- trimInlines . mconcat <$> many1 (notFollowedBy blankline >> inline) pos <- getPosition let len = sourceColumn pos - 1@@ -1044,22 +1096,15 @@ _ -> replicate numOfCols ColWidthDefault let toRow = Row nullAttr . map B.simpleCell toHeaderRow l = [toRow l | not (null l)]- return $ B.table (B.simpleCaption $ B.plain title)+ return $ compactifyTable+ $ B.table (B.simpleCaption $ B.plain title) (zip (replicate numOfCols AlignDefault) widths) (TableHead nullAttr $ toHeaderRow headerRow) [TableBody nullAttr 0 [] $ map toRow bodyRows] (TableFoot nullAttr []) -singleParaToPlain :: Blocks -> Blocks-singleParaToPlain bs =- case B.toList bs of- [Para ils] -> B.fromList [Plain ils]- _ -> bs- parseCell :: PandocMonad m => Text -> RSTParser m Blocks-parseCell t = singleParaToPlain- <$> parseFromString' parseBlocks (trim t <> "\n\n")-+parseCell t = parseFromString' parseBlocks (trim t <> "\n\n") -- TODO: -- - Only supports :format: fields with a single format for :raw: roles,@@ -1176,8 +1221,8 @@ then stripTrailingNewlines else id attribs = (ident, classes', kvs)- classes' = lang- : ["numberLines" | isJust (lookup "number-lines" fields)]+ classes' = [ lang | not (T.null lang) ]+ ++ ["numberLines" | isJust (lookup "number-lines" fields)] ++ classes kvs = [(k,v) | (k,v) <- fields, k /= "number-lines", k /= "class", k /= "id", k /= "name"]@@ -1235,7 +1280,7 @@ noteMarker :: Monad m => RSTParser m Text noteMarker = do char '['- res <- many1Char digit+ res <- takeWhile1P isDigit <|> try (char '#' >> liftM ("#" <>) simpleReferenceName) <|> countChar 1 (oneOf "#*")@@ -1275,9 +1320,9 @@ targetURI = do skipSpaces optional $ try $ newline >> notFollowedBy blankline- contents <- trim <$>- many1Char (satisfy (/='\n')- <|> try (newline >> many1 spaceChar >> noneOf " \t\n"))+ contents <- trim . mconcat <$>+ many1 (takeWhile1P (/='\n')+ <|> T.singleton <$> try (newline >> many1 spaceChar >> noneOf " \t\n")) blanklines return $ stripBackticks contents where@@ -1514,7 +1559,8 @@ gridTableWith (Identity <$> parseBlocks) table :: PandocMonad m => RSTParser m Blocks-table = gridTable <|> simpleTable False <|> simpleTable True <?> "table"+table = compactifyTable <$>+ (gridTable <|> simpleTable False <|> simpleTable True <?> "table") -- -- inline@@ -1559,7 +1605,7 @@ symbol :: Monad m => RSTParser m Inlines symbol = do- c <- oneOf specialChars+ c <- satisfy isSpecialChar unless (canPrecedeOpener c) updateLastStrPos return $ B.str $ T.singleton c @@ -1720,10 +1766,11 @@ str :: Monad m => RSTParser m Inlines str = do- let strChar = noneOf ("\t\n " ++ specialChars)- result <- many1Char strChar+ result <- takeWhile1P isStrChar updateLastStrPos return $ B.str result+ where+ isStrChar c = c /= '\t' && c /= '\n' && c /= ' ' && not (isSpecialChar c) -- an endline character that can be treated as a space, not a structural break endline :: Monad m => RSTParser m Inlines@@ -1741,8 +1788,29 @@ -- link :: PandocMonad m => RSTParser m Inlines-link = choice [explicitLink, referenceLink, autoLink] <?> "link"+link = do+ linkPossible+ choice [explicitLink, referenceLink, autoLink] <?> "link" +-- Fail fast if no link parser can succeed here: each of them+-- requires one of the characters @`[_:\@@ before the next+-- whitespace ('`' for explicit links and quoted reference names,+-- '[' for citation names, '_' for reference links, ':' after the+-- scheme of a URI, '@' in an email address). Checking this on the+-- raw input is much cheaper than running each link parser over the+-- next word only to have it fail.+linkPossible :: Monad m => RSTParser m ()+linkPossible = do+ Sources inps <- getInput+ case inps of+ [] -> mzero+ (_, t) : rest -> do+ let (w, t') = T.break (\c -> c == ' ' || c == '\t' || c == '\n') t+ guard $ (T.null t' && not (null rest))+ -- word may continue in next chunk: inconclusive+ || T.any (\c -> c == '`' || c == '[' || c == '_' ||+ c == ':' || c == '@') w+ explicitLink :: PandocMonad m => RSTParser m Inlines explicitLink = try $ do char '`'@@ -1763,7 +1831,7 @@ let label'' = if label' == mempty then B.str src else label'- let key = toKey $ stringify label'+ let key = toKey $ stringifyInlines label' unless (key == Key mempty) $ do updateState $ \s -> s{ stateKeys = M.insert key ((src',""), nullAttr) $ stateKeys s }
@@ -286,11 +286,11 @@ x <- hexDigit y <- hexDigit return $ hexToWord (T.pack [x,y])- letterSequence = T.pack <$> many1 (satisfy (\c -> isAscii c && isLetter c))+ letterSequence = takeWhile1P (\c -> isAscii c && isLetter c) unformattedText = do- ts <- filter (\c -> c /= '\r' && c /= '\n') <$>- ( many1 (satisfy (\c -> not (isSpecial c) || c == '\r' || c == '\n')))- return $! UnformattedText $! T.pack ts+ ts <- T.filter (\c -> c /= '\r' && c /= '\n') <$>+ takeWhile1P (\c -> not (isSpecial c) || c == '\r' || c == '\n')+ return $! UnformattedText ts grouped = do char '{' skipMany nl
@@ -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
@@ -13,7 +13,7 @@ ( PandocMonad(..), report, PandocMonad(..), report ) import Control.Monad ( mzero, mplus, mzero, mplus )-import Data.Char (chr, isAscii, isAlphaNum)+import Data.Char (chr, isAscii, isAlphaNum, isDigit) import qualified Data.Map as M import qualified Data.Text as T import Text.Pandoc.Logging (LogMessage(..))@@ -205,7 +205,7 @@ signedNumber :: (PandocMonad m, RoffLikeLexer x) => Lexer m x T.Text signedNumber = try $ do sign <- option "" ("-" <$ char '-' <|> "" <$ char '+')- ds <- many1Char digit+ ds <- takeWhile1P isDigit return (sign <> ds) -- Parses: [..] or (..
@@ -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
@@ -56,7 +56,7 @@ import Text.Pandoc.Parsing import Text.Pandoc.Readers.HTML (htmlTag, isBlockTag, isInlineTag) import Text.Pandoc.Readers.LaTeX (rawLaTeXBlock, rawLaTeXInline)-import Text.Pandoc.Shared (trim, tshow)+import Text.Pandoc.Shared (trim, tshow, compactifyTable) import Text.Read (readMaybe) -- | Parse a Textile text and return a Pandoc document.@@ -452,7 +452,8 @@ transpose $ map (map (snd . fst)) (headers:rows) let toRow = Row nullAttr . map B.simpleCell toHeaderRow l = [toRow l | not (null l)]- return $ B.table (B.simpleCaption $ B.plain caption)+ return $ compactifyTable+ $ B.table (B.simpleCaption $ B.plain caption) (zip aligns (replicate nbOfCols ColWidthDefault)) (TableHead nullAttr $ toHeaderRow $ map snd headers) [TableBody nullAttr 0 [] $ map (toRow . map snd) rows]@@ -679,7 +680,7 @@ let attr = case lookup "style" kvs of Just stls -> (ident, cls, pickStylesToKVs ["width", "height"] stls) Nothing -> (ident, cls, kvs)- src <- T.pack <$> many1 (noneOf " \t\n\r!(")+ src <- takeWhile1P (`notElem` (" \t\n\r!(" :: [Char])) alt <- fmap T.pack $ option "" $ try $ char '(' *> manyTill anyChar (char ')') char '!' let img = B.imageWith attr src alt (B.str alt)
@@ -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 "[" "]|" "]"
@@ -18,6 +18,7 @@ import Control.Monad (guard, void, when) import Control.Monad.Except (catchError, throwError) import Control.Monad.Reader (Reader, asks, runReader)+import Data.Char (isAlphaNum) import Data.Default import Data.List (intercalate, transpose) import Data.List.NonEmpty (nonEmpty)@@ -33,7 +34,7 @@ import Text.Pandoc.Definition import Text.Pandoc.Options import Text.Pandoc.Parsing hiding (space, spaces, uri)-import Text.Pandoc.Shared (compactify, compactifyDL)+import Text.Pandoc.Shared (compactify, compactifyDL, compactifyTable) import Text.Pandoc.URI (escapeURI) type T2T = ParsecT Sources ParserState (Reader T2TMeta)@@ -132,7 +133,7 @@ setting :: T2T (Keyword, Value) setting = do string "%!"- keyword <- ignoreSpacesCap (many1Char alphaNum)+ keyword <- ignoreSpacesCap (takeWhile1P isAlphaNum) char ':' value <- ignoreSpacesCap (manyTillChar anyChar newline) return (keyword, value)@@ -274,7 +275,8 @@ let headerPadded = if null tableHeader then mempty else pad size tableHeader let toRow = Row nullAttr . map B.simpleCell toHeaderRow l = [toRow l | not (null l)]- return $ B.table B.emptyCaption+ return $ compactifyTable+ $ B.table B.emptyCaption (zip aligns (replicate ncolumns ColWidthDefault)) (TableHead nullAttr $ toHeaderRow headerPadded) [TableBody nullAttr 0 [] $ map toRow rowsPadded]@@ -415,7 +417,7 @@ -> (Text -> a) -- Special Case to handle ****** -> T2T Inlines inlineMarkup p f c special = try $ do- start <- many1Char (char c)+ start <- takeWhile1P (== c) let l = T.length start guard (l >= 2) when (l == 2) (void $ notFollowedBy space)@@ -426,7 +428,7 @@ case body of Just middle -> do lastChar <- anyChar- end <- many1Char (char c)+ end <- takeWhile1P (== c) let parser inp = parseFromString' (mconcat <$> many p) inp let start' = case T.drop 2 start of "" -> mempty@@ -518,7 +520,7 @@ t2tURI = do start <- try ((<>) <$> proto <*> urlLogin) <|> guess domain <- many1Char chars- sep <- manyChar (char '/')+ sep <- takeWhileP (== '/') form' <- option mempty (T.cons <$> char '?' <*> many1Char form) anchor' <- option mempty (T.cons <$> char '#' <*> manyChar anchor) return (start <> domain <> sep <> form' <> anchor')@@ -528,7 +530,7 @@ guess = (<>) <$> (((<>) <$> stringAnyCase "www" <*> option mempty (T.singleton <$> oneOf "23")) <|> stringAnyCase "ftp") <*> (T.singleton <$> char '.') login = alphaNum <|> oneOf "_.-"- pass = manyChar (noneOf " @")+ pass = takeWhileP (\x -> x /= ' ' && x /= '@') chars = alphaNum <|> oneOf "%._/~:,=$@&+-" anchor = alphaNum <|> oneOf "%._0" form = chars <|> oneOf ";*"@@ -572,7 +574,7 @@ return B.softbreak str :: T2T Inlines-str = try $ B.str <$> many1Char (noneOf $ specialChars ++ "\n\r ")+str = try $ B.str <$> takeWhile1P (`notElem` (specialChars ++ "\n\r ")) whitespace :: T2T Inlines whitespace = try $ B.space <$ spaceChar
@@ -1,6 +1,7 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE LambdaCase #-}@@ -30,14 +31,14 @@ import Typst ( parseTypst, evaluateTypst ) import Text.Pandoc.Error (PandocError(..)) import Text.Pandoc.Translations (Term(References), translateTerm)-import Text.Pandoc.Shared (tshow, blocksToInlines)+import Text.Pandoc.Shared (tshow, blocksToInlines, compactifyTable) import Text.Pandoc.Parsing (registerHeader, reportLogMessages) import Control.Monad.Except (throwError) import Control.Monad (MonadPlus (mplus), void, guard, foldM) import Control.Monad.Trans (lift) import qualified Data.Foldable as F import qualified Data.Map as M-import Data.Maybe (catMaybes, fromMaybe, isJust)+import Data.Maybe (catMaybes, fromMaybe, isJust, listToMaybe) import Data.Sequence (Seq) import qualified Data.Sequence as Seq import qualified Data.Set as Set@@ -96,7 +97,7 @@ ignored ("unknown block element " <> tname <> " at " <> tshow pos) pure mempty- Just handler -> handler pos mbident fields+ Just (BlockHandler handler) -> handler pos mbident fields _ -> pure mempty pInline :: PandocMonad m => P m B.Inlines@@ -110,54 +111,130 @@ , tname /= "math.equation" -> B.math . writeTeX <$> pMathMany (Seq.singleton res) Elt name@(Identifier tname) pos fields -> do- labs <- sLabels <$> getState- labelTarget <- (do result <- getField "target" fields- case result of- VLabel t | t `elem` labs -> pure True- _ -> pure False)- <|> pure False- if tname == "ref" && not labelTarget- then do- -- @foo is a citation unless it links to a lab in the doc:- let targetToKey (Identifier "target") = Identifier "key"- targetToKey k = k- case M.lookup "cite" inlineHandlers of- Nothing -> do- ignored ("unknown inline element " <> tname <>- " at " <> tshow pos)- pure mempty- Just handler -> handler pos Nothing (M.mapKeys targetToKey fields)+ let runInlineHandler =+ case M.lookup name inlineHandlers of+ Nothing -> do+ ignored ("unknown inline element " <> tname <>+ " at " <> tshow pos)+ pure mempty+ Just (InlineHandler handler) -> handler pos Nothing fields+ if tname /= "ref"+ then runInlineHandler else do- case M.lookup name inlineHandlers of- Nothing -> do- ignored ("unknown inline element " <> tname <>- " at " <> tshow pos)- pure mempty- Just handler -> handler pos Nothing fields+ labs <- sLabels <$> getState+ labelTarget <- (do result <- getField "target" fields+ case result of+ VLabel t | t `Set.member` labs -> pure True+ _ -> pure False)+ <|> pure False+ if labelTarget+ then runInlineHandler+ else do+ -- @foo is a citation unless it links to a lab in the doc:+ let targetToKey (Identifier "target") = Identifier "key"+ targetToKey k = k+ case M.lookup "cite" inlineHandlers of+ Nothing -> do+ ignored ("unknown inline element " <> tname <>+ " at " <> tshow pos)+ pure mempty+ Just (InlineHandler handler) ->+ handler pos Nothing (M.mapKeys targetToKey fields) --- Pull block elements out of inline elements, e.g.--- Elt "smallcaps" [ Elt "heading" [..] ] ->--- Elt "heading" [ Elt "smallcaps" [..]]. See #11017.+-- Ensure that inline elements contain only inline content: split+-- them at paragraph breaks, and pull block children out, applying the+-- element to a child's own contents (#11017, #11881). A pandoc inline+-- cannot span paragraphs, so this is the closest structural rendering;+-- e.g. Elt "emph" [Txt "hi", parbreak, Txt "there"] becomes+-- Elt "emph" [Txt "hi"], parbreak, Elt "emph" [Txt "there"]. fixNesting :: Content -> Content-fixNesting el@(Elt name pos fields)- | Just (VContent elts) <- M.lookup "body" fields- = let elts' = fmap fixNesting elts- fields' = M.insert "body" (VContent elts') fields- in if isBlock el- then Elt name pos fields'- else case getField "body" fields' of- Just ([el'@(Elt name' pos' fields'')] :: Seq Content)- | isBlock el'- , not (isInline el')- , "body" `M.member` fields''- -> Elt name' pos' $- M.insert "body" (VContent- (Seq.singleton- (Elt name pos fields'')))- fields'- _ -> Elt name pos fields'+fixNesting el@(Elt name _ _)+ | Identifier tname <- name+ , "math." `T.isPrefixOf` tname = el -- math has its own grammar+fixNesting (Elt name pos fields) = Elt name pos (M.map fixVal fields) fixNesting x = x +fixVal :: Val -> Val+fixVal (VContent cs) = VContent (fixSeq cs)+fixVal (VArray vs) = VArray (fmap fixVal vs)+fixVal (VTermItem t d) = VTermItem (fixSeq t) (fixSeq d)+fixVal v = v++fixSeq :: Seq Content -> Seq Content+fixSeq = foldMap expand . fmap fixNesting++-- Split an inline element whose body contains block content.+expand :: Content -> Seq Content+expand el@(Elt name pos fields)+ | isSplittable name+ , Just (field, VContent body) <- contentField fields+ , F.any isStrictlyBlock body+ = splitInlineBody name pos fields field body+ | otherwise = Seq.singleton el+expand x = Seq.singleton x++-- Whether the element's body is parsed with pInlines, and thus cannot+-- contain block content: anything without a block handler, except+-- footnote (body parsed as blocks), the block-body table elements, and+-- math elements.+isSplittable :: Identifier -> Bool+isSplittable name@(Identifier tname) =+ name `Set.notMember` blockKeys+ && name `Set.notMember` blockBodyElements+ && not ("math." `T.isPrefixOf` tname)++-- Elements without block handlers whose content is nonetheless parsed+-- as block content.+blockBodyElements :: Set.Set Identifier+blockBodyElements = Set.fromList+ [ "footnote", "grid.cell", "table.cell", "grid.header"+ , "table.header", "grid.footer", "table.footer" ]++-- Strictly block content, not consumable by 'pInline'.+isStrictlyBlock :: Content -> Bool+isStrictlyBlock c = isBlock c && not (isInline c)++-- The element's content field. Only body and text: other content+-- fields, such as ref's supplement, are parameters rather than bodies.+contentField :: M.Map Identifier Val -> Maybe (Identifier, Val)+contentField fields =+ listToMaybe+ [ kv | kv@(k, VContent _) <- M.toAscList fields+ , k == Identifier "body" || k == Identifier "text" ]++-- Split the element's body at block content: inline runs are wrapped+-- back in the element, a parbreak separates paragraphs, and a block+-- child gets the element applied to its own body, if it has one.+splitInlineBody+ :: Identifier -> Maybe SourcePos -> M.Map Identifier Val+ -> Identifier -> Seq Content -> Seq Content+splitInlineBody name pos fields field =+ Seq.fromList . go [] . F.toList+ where+ wrap cs = Elt name pos (M.insert field (VContent (Seq.fromList cs)) fields)++ go run [] = flush run+ go run (c : cs)+ | isStrictlyBlock c = flush run ++ splitOff c ++ go [] cs+ | otherwise = go (c : run) cs++ flush run = [ wrap (reverse run) | not (null run) ]++ splitOff c+ | isParbreak c = [Elt "parbreak" pos mempty]+ | otherwise = case c of+ Elt bname bpos bfields+ | Just (VContent inner) <- M.lookup (Identifier "body") bfields ->+ [ Elt bname bpos+ ( M.insert (Identifier "body")+ (VContent (expand (wrap (F.toList inner))))+ bfields ) ]+ _ -> [c]++isParbreak :: Content -> Bool+isParbreak (Elt "parbreak" _ _) = True+isParbreak _ = False+ pPandoc :: PandocMonad m => P m B.Pandoc pPandoc = do Elt "document" _ fields <- pTok isDocument@@ -231,28 +308,35 @@ isInline Txt{} = True blockKeys :: Set.Set Identifier-blockKeys = Set.fromList $ M.keys- (blockHandlers :: M.Map Identifier- (Maybe SourcePos -> Maybe Text ->- M.Map Identifier Val -> P PandocPure B.Blocks))+blockKeys = Set.fromList $ M.keys blockHandlers inlineKeys :: Set.Set Identifier-inlineKeys = Set.fromList $ M.keys- (inlineHandlers :: M.Map Identifier- (Maybe SourcePos -> Maybe Text ->- M.Map Identifier Val -> P PandocPure B.Inlines))+inlineKeys = Set.fromList $ M.keys inlineHandlers -blockHandlers :: PandocMonad m =>- M.Map Identifier- (Maybe SourcePos -> Maybe Text ->- M.Map Identifier Val -> P m B.Blocks)+-- The handler maps are wrapped in newtypes with polymorphic fields so+-- that the maps themselves are monomorphic. This guarantees that they+-- are constant applicative forms, constructed only once; a+-- @PandocMonad m => M.Map ...@ would be a function taking a typeclass+-- dictionary, liable to be rebuilt at each lookup.++newtype BlockHandler = BlockHandler+ (forall m. PandocMonad m+ => Maybe SourcePos -> Maybe Text -> M.Map Identifier Val+ -> P m B.Blocks)++newtype InlineHandler = InlineHandler+ (forall m. PandocMonad m+ => Maybe SourcePos -> Maybe Text -> M.Map Identifier Val+ -> P m B.Inlines)++blockHandlers :: M.Map Identifier BlockHandler blockHandlers = M.fromList- [("text", \_ _ fields -> do+ [("text", BlockHandler $ \_ _ fields -> do body <- getField "body" fields -- sometimes text elements include para breaks notFollowedBy $ void $ pWithContents pInlines body pWithContents pBlocks body)- ,("title", \_ _ fields -> do+ ,("title", BlockHandler $ \_ _ fields -> do body <- getField "body" fields case body of VContent cs -> do@@ -260,16 +344,16 @@ updateState $ \s -> s{ sMeta = B.setMeta "title" ils (sMeta s) } pure mempty _ -> pure mempty)- ,("box", \_ _ fields -> do+ ,("box", BlockHandler $ \_ _ fields -> do body <- getField "body" fields B.divWith ("", ["box"], []) <$> pWithContents pBlocks body)- ,("heading", \_ mbident fields -> do+ ,("heading", BlockHandler $ \_ mbident fields -> do body <- getField "body" fields lev <- getField "level" fields <|> pure 1 ils <- pWithContents pInlines body attr <- registerHeader (fromMaybe "" mbident,[],[]) ils pure $ B.headerWith attr lev ils)- ,("quote", \_ _ fields -> do+ ,("quote", BlockHandler $ \_ _ fields -> do getField "block" fields >>= guard body <- getField "body" fields >>= pWithContents pBlocks attribution' <- getField "attribution" fields@@ -278,11 +362,11 @@ else (\x -> B.para ("\x2014\xa0" <> x)) <$> (pWithContents pInlines attribution') pure $ B.blockQuote $ body <> attribution)- ,("list", \_ _ fields -> do+ ,("list", BlockHandler $ \_ _ fields -> do children <- V.toList <$> getField "children" fields B.bulletList <$> mapM (pWithContents pBlocks) children)- ,("list.item", \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)- ,("enum", \_ _ fields -> do+ ,("list.item", BlockHandler $ \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)+ ,("enum", BlockHandler $ \_ _ fields -> do children <- V.toList <$> getField "children" fields mbstart <- getField "start" fields start <- case mbstart of@@ -311,8 +395,8 @@ _ -> (B.DefaultStyle, B.DefaultDelim) let listAttr = (start, sty, delim) B.orderedListWith listAttr <$> mapM (pWithContents pBlocks) children)- ,("enum.item", \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)- ,("terms", \_ _ fields -> do+ ,("enum.item", BlockHandler $ \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)+ ,("terms", BlockHandler $ \_ _ fields -> do children <- V.toList <$> getField "children" fields B.definitionList <$> mapM@@ -324,38 +408,41 @@ _ -> pure (mempty, []) ) children)- ,("terms.item", \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)- ,("raw", \_ mbident fields -> do+ ,("terms.item", BlockHandler $ \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)+ ,("raw", BlockHandler $ \_ mbident fields -> do txt <- T.filter (/= '\r') <$> getField "text" fields mblang <- getField "lang" fields let attr = (fromMaybe "" mbident, maybe [] (\l -> [l]) mblang, []) pure $ B.codeBlockWith attr txt)- ,("parbreak", \_ _ _ -> pure mempty)- ,("block", \_ mbident fields ->+ ,("parbreak", BlockHandler $ \_ _ _ -> pure mempty)+ ,("par", BlockHandler $ \_ mbident fields -> do+ maybe B.para (\ident -> B.divWith (ident, [], []) . B.para) mbident+ <$> (getField "body" fields >>= pWithContents pInlines))+ ,("block", BlockHandler $ \_ mbident fields -> maybe id (\ident -> B.divWith (ident, [], [])) mbident <$> (getField "body" fields >>= pWithContents pBlocks))- ,("place", \_ _ fields -> do+ ,("place", BlockHandler $ \_ _ fields -> do ignored "parameters of place" getField "body" fields >>= pWithContents pBlocks)- ,("columns", \_ _ fields -> do+ ,("columns", BlockHandler $ \_ _ fields -> do (cnt :: Integer) <- getField "count" fields B.divWith ("", ["columns-flow"], [("count", T.pack (show cnt))]) <$> (getField "body" fields >>= pWithContents pBlocks))- ,("rect", \_ _ fields ->+ ,("rect", BlockHandler $ \_ _ fields -> B.divWith ("", ["rect"], []) <$> (getField "body" fields >>= pWithContents pBlocks))- ,("circle", \_ _ fields ->+ ,("circle", BlockHandler $ \_ _ fields -> B.divWith ("", ["circle"], []) <$> (getField "body" fields >>= pWithContents pBlocks))- ,("ellipse", \_ _ fields ->+ ,("ellipse", BlockHandler $ \_ _ fields -> B.divWith ("", ["ellipse"], []) <$> (getField "body" fields >>= pWithContents pBlocks))- ,("polygon", \_ _ fields ->+ ,("polygon", BlockHandler $ \_ _ fields -> B.divWith ("", ["polygon"], []) <$> (getField "body" fields >>= pWithContents pBlocks))- ,("square", \_ _ fields ->+ ,("square", BlockHandler $ \_ _ fields -> B.divWith ("", ["square"], []) <$> (getField "body" fields >>= pWithContents pBlocks))- ,("align", \_ _ fields -> do+ ,("align", BlockHandler $ \_ _ fields -> do alignment <- getField "alignment" fields B.divWith ("", [], [("align", repr alignment)]) <$> (getField "body" fields >>= pWithContents pBlocks))- ,("stack", \_ _ fields -> do+ ,("stack", BlockHandler $ \_ _ fields -> do (dir :: Direction) <- getField "dir" fields `mplus` pure Ltr rawchildren <- getField "children" fields children <-@@ -370,9 +457,9 @@ B.divWith ("", [], [("stack", repr (VDirection dir))]) $ mconcat $ map (B.divWith ("", [], [])) children)- ,("grid", \_ mbident fields -> parseTable mbident fields)- ,("table", \_ mbident fields -> parseTable mbident fields)- ,("figure", \_ mbident fields -> do+ ,("grid", BlockHandler $ \_ mbident fields -> parseTable mbident fields)+ ,("table", BlockHandler $ \_ mbident fields -> parseTable mbident fields)+ ,("figure", BlockHandler $ \_ mbident fields -> do body <- getField "body" fields >>= pWithContents pBlocks (mbCaption :: Maybe (Seq Content)) <- getField "caption" fields (caption :: B.Blocks) <- maybe mempty (pWithContents pBlocks) mbCaption@@ -382,14 +469,14 @@ (B.Table attr (B.Caption Nothing (B.toList caption)) colspecs thead tbodies tfoot) _ -> B.figureWith (fromMaybe "" mbident, [], []) (B.Caption Nothing (B.toList caption)) body)- ,("line", \_ _ fields ->+ ,("line", BlockHandler $ \_ _ fields -> case ( M.lookup "start" fields >> M.lookup "end" fields >> M.lookup "angle" fields ) of Nothing -> pure B.horizontalRule _ -> pure mempty)- ,("divider", \_ _ _fields -> pure B.horizontalRule)- ,("numbering", \_ _ fields -> do+ ,("divider", BlockHandler $ \_ _ _fields -> pure B.horizontalRule)+ ,("numbering", BlockHandler $ \_ _ fields -> do numStyle <- getField "numbering" fields (nums :: V.Vector Integer) <- getField "numbers" fields let toText v = fromMaybe "" $ fromVal v@@ -402,12 +489,12 @@ Failure _ -> "?" _ -> "?" pure $ B.plain . B.text . mconcat . map toNum $ V.toList nums)- ,("footnote.entry", \_ _ fields ->+ ,("footnote.entry", BlockHandler $ \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)- ,("pad", \_ _ fields -> -- ignore paddingy+ ,("pad", BlockHandler $ \_ _ fields -> -- ignore paddingy getField "body" fields >>= pWithContents pBlocks)- ,("pagebreak", \_ _ _ -> pure $ B.divWith ("", ["page-break"], [("wrapper", "1")]) B.horizontalRule)- ,("bibliography", \_ _ fields -> do+ ,("pagebreak", BlockHandler $ \_ _ _ -> pure $ B.divWith ("", ["page-break"], [("wrapper", "1")]) B.horizontalRule)+ ,("bibliography", BlockHandler $ \_ _ fields -> do let getSources v = case v of VString t -> MetaString t VArray xs -> MetaList $ map getSources $ V.toList xs@@ -426,7 +513,7 @@ _ -> Just . B.text <$> lift (translateTerm References) let hdr = maybe mempty (B.header 1) mbTitle pure $ hdr <> B.divWith ("refs", [], []) mempty)- ,("rotate", \_ _ fields -> do+ ,("rotate", BlockHandler $ \_ _ fields -> do body <- getField "body" fields >>= pWithContents pBlocks let kvs = case M.lookup "angle" fields of Just (VAngle ang) -> [("angle", T.pack $ show ang)]@@ -434,11 +521,9 @@ pure $ B.divWith ("", ["rotate"], kvs) body) ] -inlineHandlers :: PandocMonad m =>- M.Map Identifier (Maybe SourcePos -> Maybe Text ->- M.Map Identifier Val -> P m B.Inlines)+inlineHandlers :: M.Map Identifier InlineHandler inlineHandlers = M.fromList- [("ref", \_ _ fields -> do+ [("ref", InlineHandler $ \_ _ fields -> do VLabel target <- getField "target" fields supplement' <- getField "supplement" fields supplement <- case supplement' of@@ -449,17 +534,17 @@ pure $ B.text ("[" <> target <> "]") _ -> pure mempty pure $ B.linkWith ("", ["ref"], []) ("#" <> target) "" supplement)- ,("linebreak", \_ _ _ -> pure B.linebreak)- ,("text", \_ _ fields -> do+ ,("linebreak", InlineHandler $ \_ _ _ -> pure B.linebreak)+ ,("text", InlineHandler $ \_ _ fields -> do body <- getField "body" fields (mbweight :: Maybe Text) <- getField "weight" fields case mbweight of Just "bold" -> B.strong <$> pWithContents pInlines body _ -> pWithContents pInlines body)- ,("raw", \_ _ fields -> B.code . T.filter (/= '\r') <$> getField "text" fields)- ,("footnote", \_ _ fields ->+ ,("raw", InlineHandler $ \_ _ fields -> B.code . T.filter (/= '\r') <$> getField "text" fields)+ ,("footnote", InlineHandler $ \_ _ fields -> B.note <$> (getField "body" fields >>= pWithContents pBlocks))- ,("cite", \_ _ fields -> do+ ,("cite", InlineHandler $ \_ _ fields -> do VLabel key <- getField "key" fields (form :: Text) <- getField "form" fields <|> pure "normal" let citation =@@ -468,44 +553,50 @@ B.citationPrefix = mempty, B.citationSuffix = mempty, B.citationMode = case form of+ "prose" -> B.AuthorInText "year" -> B.SuppressAuthor+ -- "author" and "full" have no pandoc+ -- equivalent; fall back to normal _ -> B.NormalCitation, B.citationNoteNum = 0, B.citationHash = 0 } pure $ B.cite [citation] (B.text $ "[" <> key <> "]"))- ,("lower", \_ _ fields -> do+ ,("lower", InlineHandler $ \_ _ fields -> do body <- getField "text" fields walk (modString T.toLower) <$> pWithContents pInlines body)- ,("upper", \_ _ fields -> do+ ,("upper", InlineHandler $ \_ _ fields -> do body <- getField "text" fields walk (modString T.toUpper) <$> pWithContents pInlines body)- ,("emph", \_ _ fields -> do+ ,("emph", InlineHandler $ \_ _ fields -> do body <- getField "body" fields B.emph <$> pWithContents pInlines body)- ,("strong", \_ _ fields -> do+ ,("strong", InlineHandler $ \_ _ fields -> do body <- getField "body" fields B.strong <$> pWithContents pInlines body)- ,("sub", \_ _ fields -> do+ ,("sub", InlineHandler $ \_ _ fields -> do body <- getField "body" fields B.subscript <$> pWithContents pInlines body)- ,("super", \_ _ fields -> do+ ,("super", InlineHandler $ \_ _ fields -> do body <- getField "body" fields B.superscript <$> pWithContents pInlines body)- ,("strike", \_ _ fields -> do+ ,("strike", InlineHandler $ \_ _ fields -> do body <- getField "body" fields B.strikeout <$> pWithContents pInlines body)- ,("smallcaps", \_ _ fields -> do+ ,("smallcaps", InlineHandler $ \_ _ fields -> do body <- getField "body" fields B.smallcaps <$> pWithContents pInlines body)- ,("underline", \_ _ fields -> do+ ,("underline", InlineHandler $ \_ _ fields -> do body <- getField "body" fields B.underline <$> pWithContents pInlines body)- ,("quote", \_ _ fields -> do+ ,("highlight", InlineHandler $ \_ _ fields -> do+ body <- getField "body" fields+ B.spanWith ("", ["mark"], []) <$> pWithContents pInlines body)+ ,("quote", InlineHandler $ \_ _ fields -> do (getField "block" fields <|> pure False) >>= guard . not- body <- getInlineBody fields >>= pWithContents pInlines+ body <- getField "body" fields >>= pWithContents pInlines pure $ B.doubleQuoted $ B.trimInlines body)- ,("link", \_ _ fields -> do+ ,("link", InlineHandler $ \_ _ fields -> do dest <- getField "dest" fields src <- case dest of VString t -> pure t@@ -530,7 +621,7 @@ pWithContents (B.fromList . blocksToInlines . B.toList <$> pBlocks) body pure $ B.link src "" description)- ,("image", \mbpos _ fields -> do+ ,("image", InlineHandler $ \mbpos _ fields -> do path <- getField "source" fields <|> getField "path" fields alt <- (B.text <$> getField "alt" fields) `mplus` pure mempty let basedir = maybe "." (takeDirectory . sourceName) mbpos@@ -550,10 +641,10 @@ ++ maybe [] (\x -> [("height", x)]) mbheight ) pure $ B.imageWith attr path' "" alt)- ,("box", \_ _ fields -> do+ ,("box", InlineHandler $ \_ _ fields -> do body <- getField "body" fields B.spanWith ("", ["box"], []) <$> pWithContents pInlines body)- ,("h", \_ _ fields -> do+ ,("h", InlineHandler $ \_ _ fields -> do amount <- getField "amount" fields `mplus` pure (LExact 1 LEm) let em = case amount of LExact x LEm -> toRational x@@ -561,21 +652,21 @@ LExact x LPt -> toRational x / 12 _ -> 1 / 3 -- guess! pure $ B.text $ getSpaceChars em)- ,("place", \_ _ fields -> do+ ,("place", InlineHandler $ \_ _ fields -> do ignored "parameters of place" getField "body" fields >>= pWithContents pInlines)- ,("align", \_ _ fields -> do+ ,("align", InlineHandler $ \_ _ fields -> do alignment <- getField "alignment" fields B.spanWith ("", [], [("align", repr alignment)]) <$> (getField "body" fields >>= pWithContents pInlines))- ,("sys.version", \_ _ _ -> pure $ B.text "typst-hs")- ,("math.equation", \_ _ fields -> do+ ,("sys.version", InlineHandler $ \_ _ _ -> pure $ B.text "typst-hs")+ ,("math.equation", InlineHandler $ \_ _ fields -> do body <- getField "body" fields display <- getField "block" fields (if display then B.displayMath else B.math) . writeTeX <$> pMathMany body)- ,("pad", \_ _ fields -> -- ignore paddingy+ ,("pad", InlineHandler $ \_ _ fields -> -- ignore paddingy getField "body" fields >>= pWithContents pInlines)- ,("rotate", \_ _ fields -> do+ ,("rotate", InlineHandler $ \_ _ fields -> do body <- getField "body" fields >>= pWithContents pInlines let kvs = case M.lookup "angle" fields of Just (VAngle ang) -> [("angle", T.pack $ show ang)]@@ -583,19 +674,6 @@ pure $ B.spanWith ("", ["rotate"], kvs) body) ] -getInlineBody :: PandocMonad m => M.Map Identifier Val -> P m (Seq Content)-getInlineBody fields =- parbreaksToLinebreaks <$> getField "body" fields--parbreaksToLinebreaks :: Seq Content -> Seq Content-parbreaksToLinebreaks =- fmap go . Seq.dropWhileL isParbreak . Seq.dropWhileR isParbreak- where- go (Elt "parbreak" pos _) = Elt "linebreak" pos mempty- go x = x- isParbreak (Elt "parbreak" _ _) = True- isParbreak _ = False- pPara :: PandocMonad m => P m B.Blocks pPara = do ils <- B.trimInlines . collapseAdjacentCites . mconcat <$> many1 pInline@@ -634,20 +712,22 @@ Cite (cs1 ++ cs2) (ils1 <> ils2) : xs go (Cite cs1 ils1) (Space : Cite cs2 ils2 : xs) = Cite (cs1 ++ cs2) (ils1 <> ils2) : xs+ go (Cite cs1 ils1) (SoftBreak : Cite cs2 ils2 : xs) =+ Cite (cs1 ++ cs2) (ils1 <> ils2) : xs go x xs = x:xs modString :: (Text -> Text) -> B.Inline -> B.Inline modString f (B.Str t) = B.Str (f t) modString _ x = x -findLabels :: Seq.Seq Content -> [Text]-findLabels = foldr go []+findLabels :: Seq.Seq Content -> Set.Set Text+findLabels = F.foldl' go Set.empty where- go (Txt{}) = id- go (Lab t) = (t :)- go (Elt{ eltFields = fs }) = \ts -> foldr go' ts fs- go' (VContent cs) = (findLabels cs ++)- go' _ = id+ go acc Txt{} = acc+ go acc (Lab t) = Set.insert t acc+ go acc (Elt{ eltFields = fs }) = F.foldl' go' acc fs+ go' acc (VContent cs) = F.foldl' go acc cs+ go' acc _ = acc parseTable :: PandocMonad m => Maybe Text -> M.Map Identifier Val -> P m B.Blocks@@ -760,14 +840,13 @@ let headRows = getRows THeader tableData let bodyRows = getRows TBody tableData let footRows = getRows TFooter tableData- pure $- B.tableWith- (fromMaybe "" mbident, [], [])- (B.Caption mempty mempty)- colspecs- (B.TableHead B.nullAttr headRows)- [B.TableBody B.nullAttr 0 [] bodyRows]- (B.TableFoot B.nullAttr footRows)+ pure $ compactifyTable $ B.tableWith+ (fromMaybe "" mbident, [], [])+ (B.Caption mempty mempty)+ colspecs+ (B.TableHead B.nullAttr headRows)+ [B.TableBody B.nullAttr 0 [] bodyRows]+ (B.TableFoot B.nullAttr footRows) data TableSection = THeader | TBody | TFooter deriving (Show, Ord, Eq)
@@ -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
@@ -66,15 +66,17 @@ import Text.Pandoc.Parsing (ParserState, ParsecT, blanklines, emailAddress, many1Till, orderedListMarker, readWithM, registerHeader, spaceChar, stateMeta,- stateOptions, uri, manyTillChar, manyChar, textStr,- many1Char, countChar, many1TillChar,+ stateOptions, uri, manyTillChar, textStr,+ countChar, many1TillChar,+ takeWhileP, takeWhile1P, alphaNum, anyChar, char, newline, noneOf, oneOf, space, spaces, string, choice, eof, lookAhead, many1, many, manyTill, notFollowedBy, skipMany1, try, option, updateState, getState, (<|>)) import Text.Pandoc.Sources (ToSources(..), Sources)-import Text.Pandoc.Shared (splitTextBy, stringify, stripFirstAndLast, tshow)+import Text.Pandoc.Shared (splitTextBy, stringifyInlines, stripFirstAndLast,+ tshow) import Text.Pandoc.URI (isURI) readVimwiki :: (PandocMonad m, ToSources a)@@ -200,7 +202,7 @@ definitionTerm :: PandocMonad m => VwParser m Inlines definitionTerm = try $ do x <- definitionTerm1 <|> definitionTerm2- guard (stringify x /= "")+ guard (stringifyInlines x /= "") return x definitionTerm1 :: PandocMonad m => VwParser m Inlines@@ -223,7 +225,7 @@ preformatted :: PandocMonad m => VwParser m Blocks preformatted = try $ do many spaceChar >> string "{{{"- attrText <- manyChar (noneOf "\n")+ attrText <- takeWhileP (/= '\n') lookAhead newline contents <- manyTillChar anyChar (try (char '\n' >> many spaceChar >> string "}}}" >> many spaceChar >> newline))@@ -233,8 +235,11 @@ makeAttr :: Text -> Attr makeAttr s =- let xs = splitTextBy (`elem` (" \t" :: String)) s in- ("", syntax xs, mapMaybe nameValue xs)+ let xs = splitTextBy (`elem` (" \t" :: String)) s+ kvs = mapMaybe nameValue xs+ cls = syntax xs ++ maybe [] T.words (lookup "class" kvs)+ ident = fromMaybe "" $ lookup "id" kvs+ in (ident, cls, [(k,v) | (k,v) <- kvs, k /= "class" && k /= "id"]) syntax :: [Text] -> [Text] syntax (s:_) | not $ T.isInfixOf "=" s = [s]@@ -479,7 +484,7 @@ inlineML = choice $ whitespace endlineML:inlineList str :: PandocMonad m => VwParser m Inlines-str = B.str <$> many1Char (noneOf $ spaceChars ++ specialChars)+str = B.str <$> takeWhile1P (`notElem` (spaceChars ++ specialChars)) whitespace :: PandocMonad m => VwParser m () -> VwParser m Inlines whitespace endline = B.space <$ (skipMany1 spaceChar <|>@@ -507,7 +512,7 @@ return $ B.spanWith (makeId contents, [], []) mempty <> B.strong contents makeId :: Inlines -> Text-makeId i = T.concat (stringify <$> toList i)+makeId i = stringifyInlines i emph :: PandocMonad m => VwParser m Inlines emph = try $ do
@@ -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)
@@ -16,16 +16,16 @@ the HTML using data URIs. -} module Text.Pandoc.SelfContained ( makeDataURI, makeSelfContained ) where-import Codec.Compression.GZip as Gzip+import qualified Codec.Compression.Zlib.Internal as Zlib import Control.Applicative ((<|>)) import Data.ByteString (ByteString) import Data.ByteString.Base64 (encode) import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy as L import qualified Data.Text as T-import Data.Char (isAlphaNum, isAscii)+import Data.Char (toLower) import Crypto.Hash (hashWith, SHA1(SHA1))-import Network.URI (escapeURIString)+import Network.URI (escapeURIString, isUnescapedInURI) import System.FilePath (takeDirectory, takeExtension, (</>)) import Text.HTML.TagSoup import Text.Pandoc.Class.PandocMonad (PandocMonad (..), fetchItem,@@ -44,19 +44,16 @@ import qualified Data.Map as M import Control.Monad.State -isOk :: Char -> Bool-isOk c = isAscii c && isAlphaNum c- makeDataURI :: (MimeType, ByteString) -> T.Text makeDataURI (mime, raw) = if textual- then "data:" <> mime' <> "," <> T.pack (escapeURIString isOk (toString raw))+ then "data:" <> mime' <> "," <> T.pack (escapeURIString isUnescapedInURI (toString raw)) else "data:" <> mime' <> ";base64," <> toText (encode raw') where textual = "text/" `T.isPrefixOf` mime raw' = if "+xml" `T.isSuffixOf` mime then B.filter (/= '\r') raw -- strip off CRs else raw- mime' = if textual && T.any (== ';') mime+ mime' = if textual && not (T.any (== ';') mime) then mime <> ";charset=utf-8" else mime -- mime type already has charset @@ -70,9 +67,10 @@ data ConvertState = ConvertState- { isHtml5 :: Bool- , svgMap :: M.Map T.Text (T.Text, [Attribute T.Text])+ { svgMap :: M.Map T.Text (T.Text, [Attribute T.Text]) -- map from hash to (id, svg attributes)+ , fetchCache :: M.Map (MimeType, T.Text) GetDataResult+ -- cache of fetched resources, keyed on mime type hint and url } deriving (Show) convertTags :: PandocMonad m =>@@ -102,7 +100,7 @@ | ("text/javascript" `T.isPrefixOf` mime || "application/javascript" `T.isPrefixOf` mime || "application/x-javascript" `T.isPrefixOf` mime) &&- not ("</script" `B.isInfixOf` bs) ->+ not ("</script" `B.isInfixOf` B.map toLower bs) -> return $ TagOpen "script" [(k,v) | (k,v) <- as , k == "type" ||@@ -155,12 +153,15 @@ Nothing -> False Just cs -> "inline-svg" `elem` cs as' <- mapM (processAttribute inlineSvgs) as- let attrs = addRole "img" $ addAriaLabel $ rights as'+ let attrs = rights as' let svgContents = lefts as' rest <- convertTags ts case svgContents of [] -> return $ TagOpen tagname attrs : rest ((hash, tags) : _) -> do+ -- inlining the SVG loses the img element's alt text, so+ -- we add role and aria-label to the svg element:+ let svgImgAttrs = addRole "img" $ addAriaLabel attrs -- drop "</img>" if present let rest' = case rest of TagClose tn : xs | tn == tagname -> xs@@ -168,7 +169,8 @@ svgmap <- gets svgMap case M.lookup hash svgmap of Just (svgid, svgattrs) -> do- let attrs' = [(k,v) | (k,v) <- combineSvgAttrs svgattrs attrs+ let attrs' = [(k,v) | (k,v) <- combineSvgAttrs svgattrs+ svgImgAttrs , k /= "id"] return $ TagOpen "svg" attrs' : TagOpen "use" [("href", "#" <> svgid),@@ -180,7 +182,7 @@ Nothing -> case dropWhile (not . isTagOpenName "svg") tags of TagOpen "svg" svgattrs : tags' -> do- let attrs' = combineSvgAttrs svgattrs attrs+ let attrs' = combineSvgAttrs svgattrs svgImgAttrs let svgid = case lookup "id" attrs' of Just id' -> id' Nothing -> "svg_" <> hash@@ -188,11 +190,7 @@ [(k,v) | (k,v) <- attrs', k /= "id"] modify $ \st -> st{ svgMap = M.insert hash (svgid, attrs'') (svgMap st) }- let fixUrl x =- case T.breakOn "url(#" x of- (_,"") -> x- (before, after) -> before <>- "url(#" <> svgid <> "_" <> T.drop 5 after+ let fixUrl = T.replace "url(#" ("url(#" <> svgid <> "_") let addIdPrefix ("id", x) = ("id", svgid <> "_" <> x) addIdPrefix (k, x) | k == "xlink:href" || k == "href" =@@ -290,7 +288,7 @@ _ -> [] cssURLs :: PandocMonad m- => FilePath -> ByteString -> m ByteString+ => FilePath -> ByteString -> StateT ConvertState m ByteString cssURLs d orig = do res <- runParserT (parseCSSUrls d) () "css" orig case res of@@ -300,21 +298,22 @@ Right bs -> return bs parseCSSUrls :: PandocMonad m- => FilePath -> ParsecT ByteString () m ByteString+ => FilePath+ -> ParsecT ByteString () (StateT ConvertState m) ByteString parseCSSUrls d = B.concat <$> P.many (pCSSWhite <|> pCSSComment <|> pCSSImport d <|> pCSSUrl d <|> pCSSOther) pCSSImport :: PandocMonad m- => FilePath -> ParsecT ByteString () m ByteString+ => FilePath+ -> ParsecT ByteString () (StateT ConvertState m) ByteString pCSSImport d = P.try $ do P.string "@import" P.spaces res <- (pQuoted <|> pUrl) >>= handleCSSUrl d P.spaces P.char ';'- P.spaces case res of- Left b -> return $ B.pack "@import " <> b+ Left b -> return $ B.pack "@import " <> b <> B.pack ";" Right (_, b) -> return b -- Note: some whitespace in CSS is significant, so we can't collapse it!@@ -334,7 +333,8 @@ (B.singleton <$> P.char '/') pCSSUrl :: PandocMonad m- => FilePath -> ParsecT ByteString () m ByteString+ => FilePath+ -> ParsecT ByteString () (StateT ConvertState m) ByteString pCSSUrl d = P.try $ do res <- pUrl >>= handleCSSUrl d case res of@@ -366,7 +366,7 @@ handleCSSUrl :: PandocMonad m => FilePath -> (T.Text, ByteString)- -> ParsecT ByteString () m+ -> ParsecT ByteString () (StateT ConvertState m) (Either ByteString (MimeType, ByteString)) handleCSSUrl d (url, fallback) = case escapeURIString (/='|') (T.unpack $ trim url) of@@ -382,7 +382,7 @@ (mt, b) <- if "text/css" `T.isPrefixOf` mt' -- see #5725: in HTML5, content type -- isn't allowed on style type attribute- then ("text/css",) <$> cssURLs d raw+ then ("text/css",) <$> lift (cssURLs d raw) else return (mt', raw) return $ Right (mt, b) CouldNotFetch _ -> return $ Left fallback@@ -393,19 +393,45 @@ | Fetched (MimeType, ByteString) deriving (Show) +-- | Decompress gzipped data, catching decompression errors instead+-- of throwing an imprecise exception from pure code.+decompressGzip :: ByteString -> Either T.Text ByteString+decompressGzip bs =+ B.concat <$>+ Zlib.foldDecompressStreamWithInput+ (\chunk rest -> (chunk :) <$> rest)+ (const (Right []))+ (Left . T.pack . show)+ (Zlib.decompressST Zlib.gzipFormat Zlib.defaultDecompressParams)+ (L.fromStrict bs)+ getData :: PandocMonad m => MimeType -> T.Text- -> m GetDataResult+ -> StateT ConvertState m GetDataResult getData mimetype src | "data:" `T.isPrefixOf` src = return $ AlreadyDataURI src -- already data: uri- | otherwise = catchError fetcher handler+ | otherwise = do+ cache <- gets fetchCache+ case M.lookup (mimetype, src) cache of+ Just res -> return res+ Nothing -> do+ res <- catchError fetcher handler+ modify $ \st ->+ st{ fetchCache = M.insert (mimetype, src) res (fetchCache st) }+ return res where fetcher = do let ext = T.toLower $ T.pack $ takeExtension $ T.unpack src (raw, respMime) <- fetchItem src- let raw' = if ext `elem` [".gz", ".svgz"]- then B.concat $ L.toChunks $ Gzip.decompress $ L.fromChunks [raw]- else raw+ if ext `elem` [".gz", ".svgz"]+ then case decompressGzip raw of+ Right raw' -> processFetched raw' respMime+ Left err -> do+ let msg = "could not decompress: " <> err+ report $ CouldNotFetchResource src msg+ return $ CouldNotFetch $ PandocSomeError msg+ else processFetched raw respMime+ processFetched raw' respMime = do let mime = case (mimetype, respMime) of ("",Nothing) -> "application/octet-stream" (x, Nothing) -> x@@ -441,10 +467,7 @@ makeSelfContained :: PandocMonad m => T.Text -> m T.Text makeSelfContained inp = do let tags = parseTags inp- let html5 = case tags of- (TagOpen "!DOCTYPE" [("html","")]:_) -> True- _ -> False- let convertState = ConvertState { isHtml5 = html5,- svgMap = mempty }+ let convertState = ConvertState { svgMap = mempty,+ fetchCache = mempty } out' <- evalStateT (convertTags tags) convertState return $ renderTags' out'
@@ -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 --
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 '/'
@@ -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.
@@ -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
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -25,7 +25,7 @@ import Control.Monad.Except (catchError) import Crypto.Hash (hashWith, SHA1(SHA1)) import qualified Data.ByteString.Lazy as BL-import Data.Char (isLetter, isSpace)+import Data.Char (isSpace, isAlphaNum) import Text.Pandoc.Char (isCJK) import Data.Ord (comparing) import Data.String (fromString)@@ -110,18 +110,60 @@ , "oMath" ] [0..]) -sortSquashed :: [Element] -> [Element]-sortSquashed l =+-- from wml.xsd EG_PPrBase+pPrTagOrder :: M.Map Text Int+pPrTagOrder =+ M.fromList+ (zip [ "pStyle"+ , "keepNext"+ , "keepLines"+ , "pageBreakBefore"+ , "framePr"+ , "widowControl"+ , "numPr"+ , "suppressLineNumbers"+ , "pBdr"+ , "shd"+ , "tabs"+ , "suppressAutoHyphens"+ , "kinsoku"+ , "wordWrap"+ , "overflowPunct"+ , "topLinePunct"+ , "autoSpaceDE"+ , "autoSpaceDN"+ , "bidi"+ , "adjustRightInd"+ , "snapToGrid"+ , "spacing"+ , "ind"+ , "contextualSpacing"+ , "mirrorIndents"+ , "suppressOverlap"+ , "jc"+ , "textDirection"+ , "textAlignment"+ , "textboxTightWrap"+ , "outlineLvl"+ , "divId"+ , "cnfStyle"+ , "rPr"+ , "sectPr"+ , "pPrChange"+ ] [0..])++sortSquashed :: M.Map Text Int -> [Element] -> [Element]+sortSquashed tagOrder l = sortBy (comparing tagIndex) l where tagIndex :: Element -> Int tagIndex el =- fromMaybe 0 (M.lookup tag rPrTagOrder)+ fromMaybe 0 (M.lookup tag tagOrder) where tag = (qName . elName) el -squashProps :: EnvProps -> [Element]-squashProps (EnvProps Nothing es) = sortSquashed es-squashProps (EnvProps (Just e) es) = sortSquashed (e : es)+squashProps :: M.Map Text Int -> EnvProps -> [Element]+squashProps tagOrder (EnvProps Nothing es) = sortSquashed tagOrder es+squashProps tagOrder (EnvProps (Just e) es) = sortSquashed tagOrder (e : es) -- | Certain characters are invalid in XML even if escaped. -- See #1992@@ -236,12 +278,18 @@ -> WS m (Text, [Element], [Element]) writeOpenXML opts (Pandoc meta blocks) = do setupTranslations meta+ -- Cache the rStyle element for each highlighting token type, so that+ -- it need not be recomputed for every Code inline. It depends only+ -- on the style maps, which don't change during writing.+ tokTypesMap <- M.fromList <$>+ mapM (\tt -> (tt,) <$> rStyleM (fromString $ show tt)) [KeywordTok ..]+ modify $ \st -> st{ stTokTypesMap = tokTypesMap } let includeTOC = writerTableOfContents opts || lookupMetaBool "toc" meta let includeLOF = writerListOfFigures opts || lookupMetaBool "lof" meta let includeLOT = writerListOfTables opts || lookupMetaBool "lot" meta abstractTitle <- case lookupMeta "abstract-title" meta of Just (MetaBlocks bs) -> pure $ stringify bs- Just (MetaInlines ils) -> pure $ stringify ils+ Just (MetaInlines ils) -> pure $ stringifyInlines ils Just (MetaString s) -> pure s _ -> translateTerm Abstract abstract <-@@ -326,9 +374,13 @@ -- | Convert a list of Pandoc blocks to OpenXML. blocksToOpenXML :: (PandocMonad m) => WriterOptions -> [Block] -> WS m [Content]-blocksToOpenXML opts =- fmap concat . mapM (blockToOpenXML opts)- . separateTables . filter (not . isForeignRawBlock)+blocksToOpenXML opts bs = do+ oldFirstPara <- gets stFirstPara+ modify $ \st -> st{ stFirstPara = True }+ result <- concat <$> mapM (blockToOpenXML opts)+ (separateTables (filter (not . isForeignRawBlock) bs))+ modify $ \st -> st{ stFirstPara = oldFirstPara }+ pure result isForeignRawBlock :: Block -> Bool isForeignRawBlock (RawBlock format _) = format /= "openxml"@@ -358,12 +410,34 @@ dynamicStyleKey :: Text dynamicStyleKey = "custom-style" +-- | Paragraph properties for a CSL-generated bibliography, derived from+-- the hints @Text.Pandoc.Citeproc@ puts on the bibliography's Div:+-- a @hanging-indent@ class and @line-spacing@/@entry-spacing@ attributes.+cslBibParaProps :: [Text] -> [(Text, Text)] -> [Element]+cslBibParaProps classes kvs =+ [ mknode "w:ind" [("w:left", "720"), ("w:hanging", "720")] ()+ | "hanging-indent" `elem` classes ] +++ [ mknode "w:spacing" spacingAttrs () | not (null spacingAttrs) ]+ where+ spacingAttrs = lineAttr ++ entryAttr+ lineAttr = case lookup "line-spacing" kvs >>= safeRead of+ Just ls | ls > (1 :: Double) ->+ [ ("w:line", tshow (round (ls * 240) :: Int))+ , ("w:lineRule", "auto") ]+ _ -> []+ entryAttr = case lookup "entry-spacing" kvs >>= safeRead of+ Just es | es > (0 :: Double) ->+ -- entry-spacing is given in em; 1 em ~ 240 twips+ [ ("w:after", tshow (round (es * 240) :: Int)) ]+ _ -> []+ -- | Convert a Pandoc block element to OpenXML. blockToOpenXML :: (PandocMonad m) => WriterOptions -> Block -> WS m [Content] blockToOpenXML opts blk = withDirection $ blockToOpenXML' opts blk blockToOpenXML' :: (PandocMonad m) => WriterOptions -> Block -> WS m [Content]-blockToOpenXML' opts (Div (ident,_classes,kvs) bs) = do+blockToOpenXML' opts (Div (ident,classes,kvs) bs) = do+ when ("math" `elem` classes) $ setFirstPara stylemod <- case lookup dynamicStyleKey kvs of Just (fromString . T.unpack -> sty) -> do modify $ \s ->@@ -384,9 +458,18 @@ let langmod = case lookup "lang" kvs of Nothing -> id Just lang -> local (\env -> env{envLang = Just lang})+ -- citeproc adds formatting hints for bibliographies generated+ -- from a CSL style; see Text.Pandoc.Citeproc (#11871).+ let isCslBib = ident == "refs" || "csl-bib-body" `elem` classes+ let cslmod = if not isCslBib+ then id+ else case cslBibParaProps classes kvs of+ [] -> id+ props -> foldr (.) id (map withParaProp props) header <- dirmod $ stylemod $ blocksToOpenXML opts hs- contents <- dirmod $ bibmod $ stylemod $ langmod $ blocksToOpenXML opts bs'+ contents <- dirmod $ bibmod $ cslmod $ stylemod $ langmod $ blocksToOpenXML opts bs' wrapBookmark ident $ header <> contents+ blockToOpenXML' opts (Header lev (ident,_,kvs) lst) = do setFirstPara let isSection = case writerTopLevelDivision opts of@@ -447,16 +530,16 @@ let displayMathPara = case lst of [x] -> isDisplayMath x _ -> False- paraProps <- getParaProps displayMathPara bodyTextStyle <- pStyleM $ if isFirstPara then "First Paragraph" else "Body Text"- let paraProps' = case paraProps of- [] -> [mknode "w:pPr" [] [bodyTextStyle]]- ps -> ps+ paraProps <- local (\env -> env{ envParaProperties =+ envParaProperties env <>+ EnvProps (Just bodyTextStyle) [] })+ (getParaProps displayMathPara) modify $ \s -> s { stFirstPara = False } contents <- inlinesToOpenXML opts lst- return [Elem $ mknode "w:p" [] (map Elem paraProps' ++ contents)]+ return [Elem $ mknode "w:p" [] (map Elem paraProps ++ contents)] blockToOpenXML' opts (LineBlock lns) = blockToOpenXML opts $ linesToPara lns blockToOpenXML' _ b@(RawBlock format str) | format == Format "openxml" = return [@@ -678,7 +761,7 @@ Nothing -> mempty Just l -> EnvProps Nothing [mknode "w:lang" [("w:val", l)] ()]- let squashed = squashProps (props <> langnode)+ let squashed = squashProps rPrTagOrder (props <> langnode) return [mknode "w:rPr" [] squashed | (not . null) squashed] withTextProp :: PandocMonad m => Element -> WS m a -> WS m a@@ -704,7 +787,7 @@ let listPr = [mknode "w:numPr" [] [ mknode "w:ilvl" [("w:val",tshow listLevel)] () , mknode "w:numId" [("w:val",tshow numid')] () ] | listLevel >= 0 && not displayMathPara]- return $ case squashProps (EnvProps Nothing listPr <> props) of+ return $ case squashProps pPrTagOrder (EnvProps Nothing listPr <> props) of [] -> [] ps -> [mknode "w:pPr" [] ps] @@ -754,8 +837,8 @@ inlineToOpenXML' :: PandocMonad m => WriterOptions -> Inline -> WS m [Content] inlineToOpenXML' _ (Str str) = map Elem <$> formattedString str-inlineToOpenXML' opts Space = inlineToOpenXML opts (Str " ")-inlineToOpenXML' opts SoftBreak = inlineToOpenXML opts (Str " ")+inlineToOpenXML' opts Space = inlineToOpenXML' opts (Str " ")+inlineToOpenXML' opts SoftBreak = inlineToOpenXML' opts (Str " ") inlineToOpenXML' opts (Span ("",["mark"],[]) ils) = withTextProp (mknode "w:highlight" [("w:val","yellow")] ()) $ inlinesToOpenXML opts ils@@ -773,16 +856,13 @@ inlineToOpenXML' opts (Span ("",["csl-indent"],[]) ils) = inlinesToOpenXML opts ils inlineToOpenXML' _ (Span (ident,["comment-start"],kvs) ils) = do- -- prefer the "id" in kvs, since that is the one produced by the docx- -- reader.- let ident' = fromMaybe ident (lookup "id" kvs)- kvs' = filter (("id" /=) . fst) kvs+ let ident' = fromMaybe ident (lookup "comment-id" kvs <|> lookup "id" kvs)+ kvs' = filter ((\x -> x /= "comment-id" && x /= "id") . fst) kvs modify $ \st -> st{ stComments = (("id",ident'):kvs', ils) : stComments st } return [ Elem $ mknode "w:commentRangeStart" [("w:id", ident')] () ] inlineToOpenXML' opts (Span (ident,["comment-end"],kvs) content) = do- -- prefer the "id" in kvs, since that is the one produced by the docx- -- reader.- let ident' = fromMaybe ident (lookup "id" kvs)+ -- now we use comment-id, but support id for legacy compat:+ let ident' = fromMaybe ident (lookup "comment-id" kvs <|> lookup "id" kvs) -- process nested content: see #8189 nestedContent <- inlinesToOpenXML opts content let thisCommentEnd =@@ -887,15 +967,14 @@ Left il -> inlineToOpenXML' opts il inlineToOpenXML' opts (Cite _ lst) = inlinesToOpenXML opts lst inlineToOpenXML' opts (Code attrs str) = do- let alltoktypes = [KeywordTok ..]- tokTypesMap <- mapM (\tt -> (,) tt <$> rStyleM (fromString $ show tt)) alltoktypes+ tokTypesMap <- gets stTokTypesMap let unhighlighted = (map Elem . intercalate [br]) `fmap` mapM formattedString (T.lines str) formatOpenXML _fmtOpts = intercalate [br] . map (map toHlTok) toHlTok (toktype,tok) = mknode "w:r" [] [ mknode "w:rPr" [] $- maybeToList (lookup toktype tokTypesMap)+ maybeToList (M.lookup toktype tokTypesMap) , mknode "w:t" [("xml:space","preserve")] tok ] let highlighted = case highlight (writerSyntaxMap opts) formatOpenXML attrs str of@@ -911,7 +990,6 @@ inlineToOpenXML' opts (Note bs) = do notes <- gets stFootnotes notenum <- getUniqueId- oldFirstPara <- gets stFirstPara footnoteStyle <- rStyleM "Footnote Reference" let notemarker = mknode "w:r" [] [ mknode "w:rPr" [] footnoteStyle@@ -927,19 +1005,19 @@ , envInNote = True }) (withParaPropM (pStyleM "Footnote Text") $ blocksToOpenXML opts $ insertNoteRef bs)- modify $ \s -> s{ stFirstPara = oldFirstPara } let newnote = mknode "w:footnote" [("w:id", notenum)] contents modify $ \s -> s{ stFootnotes = newnote : notes } return [ Elem $ mknode "w:r" [] [ mknode "w:rPr" [] footnoteStyle , mknode "w:footnoteReference" [("w:id", notenum)] () ] ] -- internal link:-inlineToOpenXML' opts (Link _ txt (T.uncons -> Just ('#', xs),_)) = do+inlineToOpenXML' opts (Link _ txt (T.uncons -> Just ('#', xs),title)) = do contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt return- [ Elem $ mknode "w:hyperlink" [("w:anchor", toBookmarkName xs)] contents ]+ [ Elem $ mknode "w:hyperlink"+ (("w:anchor", toBookmarkName xs) : tooltipAttr title) contents ] -- external link:-inlineToOpenXML' opts (Link _ txt (src,_)) = do+inlineToOpenXML' opts (Link _ txt (src,title)) = do contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt extlinks <- gets stExternalLinks id' <- case M.lookup src extlinks of@@ -949,7 +1027,8 @@ modify $ \st -> st{ stExternalLinks = M.insert src i extlinks } return i- return [ Elem $ mknode "w:hyperlink" [("r:id",id')] contents ]+ return [ Elem $ mknode "w:hyperlink" (("r:id",id') : tooltipAttr title)+ contents ] inlineToOpenXML' opts (Image attr@(imgident, _, _) alt (src, title)) = do pageWidth <- asks envPrintWidth imgs <- gets stImages@@ -995,10 +1074,23 @@ (xpt,ypt) = desiredSizeInPoints opts attr (either (const def) id (imageSize opts img)) -- 12700 emu = 1 pt- pageWidthPt = case dimension Width attr of- Just (Percent a) -> pageWidth * floor (a * 127)- _ -> pageWidth * 12700- (xemu,yemu) = fitToPage (xpt * 12700, ypt * 12700) pageWidthPt+ pageWidthPt = fromIntegral pageWidth+ pageWidthEmu = pageWidth * 12700+ (xpt', ypt') =+ case (dimension Width attr, dimension Height attr) of+ (Just (Percent a), Just (Percent b))+ -> ((a / 100.0) * pageWidthPt, (b / 100.0) * pageWidthPt)+ -- note, should use pageHeightPt but we don't have this+ -- information.+ (Just (Percent a), _)+ -> ((a / 100.0) * pageWidthPt,+ (a / 100.0) * pageWidthPt * (ypt / xpt))+ (_, Just (Percent b))+ -> ((b / 100.0) * pageWidthPt * (xpt / ypt),+ (b / 100.0) * pageWidthPt)+ (_, _) -> (xpt, ypt)+ (xemu,yemu) = fitToPage (xpt' * 12700,+ ypt' * 12700) pageWidthEmu cNvPicPr = mknode "pic:cNvPicPr" [] $ mknode "a:picLocks" [("noChangeArrowheads","1") ,("noChangeAspect","1")] ()@@ -1041,7 +1133,7 @@ , mknode "wp:effectExtent" [("b","0"),("l","0"),("r","0"),("t","0")] () , mknode "wp:docPr"- [ ("descr", stringify alt)+ [ ("descr", stringifyInlines alt) , ("title", title) , ("id", docprid) , ("name","Picture")@@ -1102,11 +1194,17 @@ -- We want to clean all bidirection (bidi) and right-to-left (rtl) -- properties from the props first. This is because we don't want -- them to stack up.- let paraProps' = filter (\e -> (qName . elName) e /= "bidi") (otherElements paraProps)+ let hasBidi = any (\e -> (qName . elName) e == "bidi") (otherElements paraProps)+ hasRtl = any (\e -> (qName . elName) e == "rtl") (otherElements textProps)+ paraProps' = filter (\e -> (qName . elName) e /= "bidi") (otherElements paraProps) textProps' = filter (\e -> (qName . elName) e /= "rtl") (otherElements textProps) paraStyle = styleElement paraProps textStyle = styleElement textProps- if isRTL+ if not isRTL && not hasBidi && not hasRtl+ -- fast path: LTR with no bidi/rtl props to remove, so the+ -- environment is unchanged; skip the 'local' rebuild.+ then x+ else if isRTL -- if we are going right-to-left, we (re?)add the properties. then flip local x $ \env -> env { envParaProperties = EnvProps paraStyle $ mknode "w:bidi" [] () : paraProps'@@ -1127,20 +1225,35 @@ return $ Elem bookmarkStart : contents ++ [Elem bookmarkEnd] -- Word imposes a 40 character limit on bookmark names and requires--- that they begin with a letter. So we just use a hash of the--- identifier when otherwise we'd have an illegal bookmark name.+-- that they begin with a letter or @_@ and contain only letters,+-- numbers or underscores. Bookmarks beginning with @_@ are+-- hidden in the user interface (and in particular hidden from screen+-- readers, which we want); these are to be used for cross-references.+-- When the id is otherwise illegal we use a hash of the identifier. toBookmarkName :: Text -> Text toBookmarkName s- | Just (c, _) <- T.uncons s- , isLetter c- , T.length s <= 40 = s- | otherwise = T.pack $ 'X' : drop 1 (show (hashWith SHA1 (fromText s)))+ | T.length s < 40+ , T.all (\c -> isAlphaNum c || c == '_') s+ = "_" <> s+ | otherwise = "_" <> T.pack (drop 1 (show (hashWith SHA1 (fromText s))))+ -- we drop 1 because a SHA1 is 40 characters and we need room for the `_` +-- A link's title is written as its ScreenTip (@w:tooltip@).+tooltipAttr :: Text -> [(Text, Text)]+tooltipAttr title = [("w:tooltip", title) | not (T.null title)]+ maxListLevel :: Int maxListLevel = 8 +-- Merge adjacent Strs, and any Space between two Strs, into a single+-- Str. Chunks are accumulated and concatenated all at once, to avoid+-- quadratic copying when a long Str/Space sequence (e.g. an entire+-- paragraph) collapses into one Str. convertSpace :: [Inline] -> [Inline]-convertSpace (Str x : Space : Str y : xs) = convertSpace (Str (x <> " " <> y) : xs)-convertSpace (Str x : Str y : xs) = convertSpace (Str (x <> y) : xs)-convertSpace (x:xs) = x : convertSpace xs-convertSpace [] = []+convertSpace (Str x : xs) = go [x] xs+ where+ go acc (Str y : ys) = go (y : acc) ys+ go acc (Space : Str y : ys) = go (y : " " : acc) ys+ go acc ys = Str (T.concat (reverse acc)) : convertSpace ys+convertSpace (x:xs) = x : convertSpace xs+convertSpace [] = []
@@ -178,7 +178,9 @@ rowwidth = round (fullrow * sum widths) :: Int widthToTwips w = floor (textwidth * w) :: Int mkGridCol w = mknode "w:gridCol" [("w:w", tshow (widthToTwips w))] ()- in if all (== 0) widths+ -- A "mixed" table with both default and given widths is treated as+ -- a table with all default widths.+ in if any (== 0) widths then ( replicate ncols $ mkGridCol (1.0 / fromIntegral ncols) , [ ("w:type", "auto"), ("w:w", "0")]) else ( map mkGridCol widths
@@ -27,6 +27,7 @@ import Control.Monad.Reader import Control.Monad.State.Strict import Data.Text (Text)+import Skylighting (TokenType) import Text.Pandoc.Class.PandocMonad (PandocMonad) import Text.Pandoc.Definition import Text.Pandoc.MIME (MimeType)@@ -68,7 +69,7 @@ data EnvProps = EnvProps{ styleElement :: Maybe Element , otherElements :: [Element]- }+ } deriving (Show) instance Semigroup EnvProps where EnvProps s es <> EnvProps s' es' = EnvProps (s <|> s') (es ++ es')@@ -87,7 +88,7 @@ , envInNote :: Bool , envChangesAuthor :: Text , envChangesDate :: Text- , envPrintWidth :: Integer+ , envPrintWidth :: Integer -- in points , envLang :: Maybe Text , envSectPr :: Maybe Element }@@ -120,6 +121,9 @@ , stInsId :: Int , stDelId :: Int , stStyleMaps :: StyleMaps+ , stTokTypesMap :: M.Map TokenType Element+ -- ^ cached rStyle element for each highlighting token type;+ -- computed once from stStyleMaps at the start of writing , stFirstPara :: Bool , stFirstSectionHeader :: Bool -- ^ True until first section header is processed , stNumIdUsed :: Bool -- ^ True if the current numId (envListNumId) has been used.@@ -146,6 +150,7 @@ , stInsId = 1 , stDelId = 1 , stStyleMaps = StyleMaps M.empty M.empty+ , stTokTypesMap = M.empty , stFirstPara = False , stFirstSectionHeader = True , stNumIdUsed = False
@@ -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] ++
@@ -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"
@@ -48,7 +48,7 @@ import Text.Pandoc.URI (urlEncode) import Numeric (showHex) import Text.DocLayout (render, literal, Doc)-import Text.Blaze.Internal (MarkupM (Empty), customLeaf, customParent)+import Text.Blaze.Internal (MarkupM (Append, Empty), customLeaf, customParent) import Text.DocTemplates (FromContext (lookupContext), Context (..), Val(..)) import qualified Text.DocTemplates.Internal as DT import Text.Blaze.Html hiding (contents)@@ -125,20 +125,24 @@ strToHtml :: Text -> Html strToHtml t- | T.any isSpecial t =- let !x = L.foldl' go mempty $ T.groupBy samegroup t- in x+ | T.any isSpecial t = go t | otherwise = toHtml t where- samegroup c d = d == '\xFE0E' || not (isSpecial c || isSpecial d) isSpecial '\'' = True isSpecial '"' = True isSpecial c = needsVariationSelector c- go h "\'" = h <> preEscapedString "\'"- go h "\"" = h <> preEscapedString "\""- go h txt | T.length txt == 1 && T.all needsVariationSelector txt- = h <> preEscapedString (T.unpack txt <> "\xFE0E")- go h txt = h <> toHtml txt+ go s =+ let (plain, rest) = T.break isSpecial s+ html = if T.null plain then mempty else toHtml plain+ in case T.uncons rest of+ Nothing -> html+ Just ('\'', rest') -> html <> preEscapedText "'" <> go rest'+ Just ('"', rest') -> html <> preEscapedText "\"" <> go rest'+ Just (c, rest')+ -- don't add a variation selector if one is already there:+ | T.take 1 rest' == "\xFE0E" -> html <> toHtml c <> go rest'+ | otherwise -> html <> preEscapedText (T.pack [c, '\xFE0E'])+ <> go rest' -- See #5469: this prevents iOS from substituting emojis. needsVariationSelector :: Char -> Bool@@ -150,6 +154,14 @@ nl :: Html nl = preEscapedString "\n" +-- | True if the markup contains no content at all. 'mconcat' and+-- '<>' on 'MarkupM' build 'Append' nodes without collapsing empty+-- markup, so simply matching on 'Empty' is not enough.+isEmptyMarkup :: MarkupM a -> Bool+isEmptyMarkup (Empty _) = True+isEmptyMarkup (Append x y) = isEmptyMarkup x && isEmptyMarkup y+isEmptyMarkup _ = False+ -- | Convert Pandoc document to Html 5 string. writeHtml5String :: PandocMonad m => WriterOptions -> Pandoc -> m Text writeHtml5String = writeHtmlString'@@ -286,7 +298,7 @@ (fmap layoutMarkup . blockListToHtml opts) (fmap layoutMarkup . inlineListToHtml opts) meta- let stringifyHTML = escapeStringForXML . stringify+ let stringifyHTML = escapeStringForXML . stringifyInlines let authsMeta = map (literal . stringifyHTML) $ docAuthors meta let dateMeta = stringifyHTML $ docDate meta let descriptionMeta = literal $ escapeStringForXML $@@ -352,7 +364,7 @@ ] nl H.link ! A.rel "stylesheet" !- A.href (toValue $ toURI html5 url <> "katex.min.css")+ A.href (toValue $ toURI html5 $ url <> "katex.min.css") _ -> mempty let mCss :: Maybe [Text] = lookupContext "css" metadata@@ -572,7 +584,7 @@ let container x | html5 , epubVersion == Just EPUB3- = H5.section ! A.id (fromString idName)+ = H5.section ! prefixedId opts (fromString idName) ! A.class_ className ! customAttribute "epub:type" "footnotes" $ x | html5@@ -633,16 +645,24 @@ (linkText, altText) = if txt == T.drop 7 s' -- autolink then ("e", name' <> " at " <> domain')- else ("'" <> obfuscateString txt <> "'",+ else ("'" <>+ T.replace "</" "<\\/" (obfuscateMarkup txt) <> "'", txt <> " (" <> name' <> " at " <> domain' <> ")")- (_, classNames, _) = attr+ (ident, classNames, kvs) = attr classNamesStr = T.concat $ map (" "<>) classNames+ otherAttrsStr = T.concat $+ [ " id=\"" <>+ escapeJSAttrVal (writerIdentifierPrefix opts <> ident) <>+ "\"" | not (T.null ident) ] +++ [ " " <> k <> "=\"" <> escapeJSAttrVal v <> "\""+ | (k, v) <- kvs ] in case meth of ReferenceObfuscation ->- -- need to use preEscapedString or &'s are escaped to & 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 &+ 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+ '&' -> "&"+ '<' -> "<"+ '>' -> ">"+ '"' -> """+ '\'' -> "'"+ '\\' -> "\"+ '\n' -> " "+ '\r' -> " "+ _ -> 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
@@ -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
@@ -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 =
@@ -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')
@@ -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) ->
@@ -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{" :
@@ -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
@@ -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
@@ -754,8 +754,8 @@ (i,c,kv) | not (null alt) , Nothing <- lookup "alt" kv- , stringify descr /= stringify alt ->- (i, c, ("alt", stringify alt) : kv)+ , stringifyInlines descr /= stringifyInlines alt ->+ (i, c, ("alt", stringifyInlines alt) : kv) _ -> imgAttr' contents <- inlineListToMarkdown opts [Image imgAttr'' descr tgt'] return $ contents <> blankline
@@ -678,13 +678,13 @@ where result = "[" <> linktext <> "](" <> (literal src) <> ")" attributes = addKeyValueToAttr attr ("title", tit) -- Use wikilinks where possible- _ | src == stringify txt && useWikilink ->- return $ "[[" <> literal (stringify txt) <> "]]"+ _ | src == stringifyInlines txt && useWikilink ->+ return $ "[[" <> literal (stringifyInlines txt) <> "]]" | useAuto -> return $ "<" <> literal srcSuffix <> ">" | useWikilink && isEnabled Ext_wikilinks_title_after_pipe opts -> return $- "[[" <> literal src <> "|" <> literal (stringify txt) <> "]]"+ "[[" <> literal src <> "|" <> literal (stringifyInlines txt) <> "]]" | useWikilink && isEnabled Ext_wikilinks_title_before_pipe opts -> return $- "[[" <> literal (stringify txt) <> "|" <> literal src <> "]]"+ "[[" <> literal (stringifyInlines txt) <> "|" <> literal src <> "]]" | useRefLinks -> let first = "[" <> linktext <> "]" second = if getKey linktext == getKey reftext
@@ -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 $
@@ -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
@@ -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
@@ -38,7 +38,7 @@ HighlightMethod(Skylighting, DefaultHighlighting)) import Text.Pandoc.Highlighting (defaultStyle) import Text.DocLayout-import Text.Pandoc.Shared (stringify, tshow)+import Text.Pandoc.Shared (stringify, stringifyInlines, tshow) import Text.Pandoc.Version (pandocVersionText) import Text.Pandoc.Writers.Shared (lookupMetaString, lookupMetaBlocks, fixDisplayMath, getLang,@@ -165,7 +165,7 @@ ,("office:version","1.3")] ( inTags True "office:meta" [] ( metaTag "meta:generator" ("Pandoc/" <> pandocVersionText) $$- metaTag "dc:title" (stringify title)+ metaTag "dc:title" (stringifyInlines title) $$ metaTag "dc:description" (T.intercalate "\n" (map stringify $@@ -184,7 +184,7 @@ $$ metaTag "meta:creation-date" d $$ metaTag "dc:date" d ) (T.pack $ formatTime defaultTimeLocale "%FT%XZ" utctime)- (T.intercalate "; " (map stringify authors))+ (T.intercalate "; " (map stringifyInlines authors)) $$ vcat userDefinedMeta )
@@ -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
@@ -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)
@@ -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
@@ -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 =
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -33,7 +33,8 @@ import Text.Pandoc.Writers.Shared ( lookupMetaInlines, lookupMetaString, metaToContext, defField, resetField, setupTranslations )-import Text.Pandoc.Shared (isTightList, orderedListMarkers, tshow, stringify)+import Text.Pandoc.Shared (isTightList, orderedListMarkers, tshow,+ stringifyInlines) import Text.Pandoc.Highlighting (highlight, formatTypstBlock, formatTypstInline, styleToTypst) import Text.Pandoc.Translations (Term(Abstract), translateTerm)@@ -362,17 +363,18 @@ $$ footer ) $$ ")"- return $ if "typst:no-figure" `elem` tabclasses- then toTypstBracesSetText typstTextAttrs table- else "#figure("- $$- nest 2- ("align(center)[" <> toTypstPoundSetText typstTextAttrs <> "#" <> table <> "]"- $$ capt'- $$ typstFigureKind- $$ ")")- $$ lab- $$ blankline+ return $+ (if "typst:no-figure" `elem` tabclasses+ then toTypstBracesSetText typstTextAttrs table+ else "#figure("+ $$+ nest 2+ ("align(center)[" <> toTypstPoundSetText typstTextAttrs <> "#" <> table <> "]"+ $$ capt'+ $$ typstFigureKind+ $$ ")"))+ $$ lab+ $$ blankline Figure (ident,_,kvs) (Caption _mbshort capt) blocks -> do caption <- blocksToTypst capt opts <- gets stOptions@@ -402,7 +404,7 @@ contents <- blocksToTypst blocks return $ "#block" <> toTypstPropsListParens typstAttrs <> "[" $$ toTypstPoundSetText typstTextAttrs- $$ contents+ $$ chomp contents $$ ("]" <+> lab) defListItemToTypst :: PandocMonad m => ([Inline], [[Block]]) -> TW m (Doc Text)@@ -410,7 +412,7 @@ modify $ \st -> st{ stEscapeContext = TermContext } term' <- inlinesToTypst term modify $ \st -> st{ stEscapeContext = NormalContext }- defns' <- mapM blocksToTypst defns+ defns' <- mapM (fmap chomp . blocksToTypst) defns return $ case defns of [[Plain _]] -> hang 4 (nowrap ("/ " <> term' <> ": ")) (vcat defns')@@ -598,7 +600,7 @@ Just alt -> Just alt Nothing -> case imgInlines of [] -> Nothing- _ -> Just (stringify imgInlines)+ _ -> Just (stringifyInlines imgInlines) textstyle :: PandocMonad m => Doc Text -> [Inline] -> TW m (Doc Text) textstyle s inlines = do
@@ -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
@@ -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
@@ -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)+ ]+ ]+ ]
@@ -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)
@@ -116,6 +116,10 @@ "docx/links.docx" "docx/links.native" , testCompare+ "hyperlinks with ScreenTips"+ "docx/link_tooltips.docx"+ "docx/link_tooltips.native"+ , testCompare "hyperlinks in <w:instrText> tag" "docx/instrText_hyperlink.docx" "docx/instrText_hyperlink.native"
@@ -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" =?>
@@ -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:"
@@ -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")
@@ -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]" , ""
@@ -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 =
@@ -64,6 +64,22 @@ def "docx/links.native" "docx/golden/links.docx"+ , testCase "link titles become ScreenTips (#11869)" $ do+ doc <- documentXml def $ Pandoc mempty+ [ Para [ Link ("", [], []) [Str "external"]+ ("https://example.org/r.pdf", "Annual report")+ , Space+ , Link ("", [], []) [Str "internal"]+ ("#methods", "Jump to Methods")+ , Space+ , Link ("", [], []) [Str "untitled"]+ ("https://example.org", "")+ ]+ , Header 1 ("methods", [], []) [Str "Methods"]+ ]+ map (findAttr (wmlName "tooltip"))+ (findElements (wmlName "hyperlink") doc)+ @?= [Just "Annual report", Just "Jump to Methods", Nothing] , docxTest "inline image" def{ writerExtensions =@@ -107,6 +123,11 @@ def "docx/lists.native" "docx/golden/lists.docx"+ , docxTest+ "CSL bibliography (hanging indent and spacing)"+ def+ "docx/csl_bibliography.native"+ "docx/golden/csl_bibliography.docx" , docxTest "lists continuing after interruption" def
@@ -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
@@ -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.
@@ -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 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 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" ] ] ] ] ]
@@ -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>
@@ -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>
@@ -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" ] ] ```
@@ -7,7 +7,6 @@ #block[ #set text(lang: "en"); This text should be in English.- ] ``` @@ -20,7 +19,6 @@ #block[ #set text(lang: "fr"); Ce texte devrait être en français.- ] ``` @@ -33,7 +31,6 @@ #block[ #set text(lang: "de"); Dieser Text sollte auf Deutsch sein.- ] ``` @@ -45,6 +42,5 @@ ^D #block[ This should not have lang set.- ] ```
@@ -11,6 +11,5 @@ + item + item + item- ] ```
@@ -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>
@@ -6,7 +6,6 @@ ^D #block[ Hello- ] <foo> ```
@@ -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>++```+
@@ -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>++```++
@@ -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").+```
@@ -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>++```
@@ -0,0 +1,11 @@+# Heading 1++++> a blockquote+>+> +>+> with an image reference+>+> 
@@ -0,0 +1,8 @@+# Heading 2++++1. A list++ +
@@ -0,0 +1,7 @@+# Heading 3++++1. A list+ + 
@@ -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 "."+ ]+]++```
@@ -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} ```
@@ -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>
@@ -0,0 +1,7 @@+```+% pandoc command/2623.odt+^D+<p><em>Italics</em></p>+<p>not <em>italics</em></p>+```+
binary file changed (absent → 9375 bytes)
@@ -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>
@@ -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. ```
@@ -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'" ] ] ```
@@ -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} ```
@@ -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>
@@ -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"} ```
@@ -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"
@@ -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%" />
@@ -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"
@@ -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>
@@ -10,7 +10,7 @@ | Sum | 8£ | +======+=======+ ^D-<table style="width:21%;">+<table style="width:20%;"> <colgroup> <col style="width: 9%" /> <col style="width: 11%" />
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -25,7 +25,7 @@  ^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> ```
@@ -7,7 +7,6 @@ ^D #block[ test- ] #label("my label") See #link(label("my label"))[my label].
@@ -2,6 +2,6 @@ % pandoc --embed-resources  ^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> ```
@@ -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>
@@ -4,7 +4,7 @@ > A suggestion. ^D [ Div- ( "" , [ "tip" ] , [] )+ ( "" , [ "alert" , "tip" ] , [] ) [ Div ( "" , [ "title" ] , [] ) [ Para [ Str "Tip" ] ] , Para [ Str "A" , Space , Str "suggestion." ] ]
@@ -6,8 +6,6 @@ *#foo* ^D [ Para- [ Strong- [ SoftBreak , Str "bar" , Space , Str "baz" , SoftBreak ]- ]+ [ Strong [ SoftBreak , Str "bar" , Space , Str "baz" ] ] ] ```
@@ -35,6 +35,7 @@ issued: 2004-04-05 type: article-journal - id: item5-1+ issued: "-001" type: article-journal - id: item5-2 issued: "-0876"
@@ -18,7 +18,7 @@ outformats="ansi asciidoc asciidoc_legacy asciidoctor bbcode bbcode_fluxbb bbcode_hubzilla bbcode_phpbb bbcode_steam bbcode_xenforo beamer biblatex bibtex chunkedhtml commonmark commonmark_x context csljson djot docbook docbook4 docbook5 docx dokuwiki dzslides epub epub2 epub3 fb2 gfm haddock html html4 html5 icml ipynb jats jats_archiving jats_articleauthoring jats_publishing jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict markua mediawiki ms muse native odt opendocument opml org pdf plain pptx revealjs rst rtf s5 slideous slidy t2t tei texinfo textile typst vimdoc xml xwiki zimwiki" highlight_styles="pygments tango espresso zenburn kate monochrome breezedark haddock" math_methods="plain mathml webtex mathjax katex gladtex"- datafiles="reference.docx reference.odt reference.pptx MANUAL.txt docx/_rels/.rels pptx/_rels/.rels abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml"+ datafiles="MANUAL.txt abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/_rels/.rels docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/_rels/.rels pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml reference.docx reference.odt reference.pptx templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml" case "${prev}" in -f|-r|--from|--read)@@ -247,7 +247,7 @@ '--list-highlight-styles[List highlighting styles]' '-D[Format to print template for]:FORMAT:(ansi asciidoc asciidoc_legacy asciidoctor bbcode bbcode_fluxbb bbcode_hubzilla bbcode_phpbb bbcode_steam bbcode_xenforo beamer biblatex bibtex chunkedhtml commonmark commonmark_x context csljson djot docbook docbook4 docbook5 docx dokuwiki dzslides epub epub2 epub3 fb2 gfm haddock html html4 html5 icml ipynb jats jats_archiving jats_articleauthoring jats_publishing jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict markua mediawiki ms muse native odt opendocument opml org pdf plain pptx revealjs rst rtf s5 slideous slidy t2t tei texinfo textile typst vimdoc xml xwiki zimwiki)' '--print-default-template[Format to print template for]:FORMAT:(ansi asciidoc asciidoc_legacy asciidoctor bbcode bbcode_fluxbb bbcode_hubzilla bbcode_phpbb bbcode_steam bbcode_xenforo beamer biblatex bibtex chunkedhtml commonmark commonmark_x context csljson djot docbook docbook4 docbook5 docx dokuwiki dzslides epub epub2 epub3 fb2 gfm haddock html html4 html5 icml ipynb jats jats_archiving jats_articleauthoring jats_publishing jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict markua mediawiki ms muse native odt opendocument opml org pdf plain pptx revealjs rst rtf s5 slideous slidy t2t tei texinfo textile typst vimdoc xml xwiki zimwiki)'- '--print-default-data-file[Data file to print]:FILE:(reference.docx reference.odt reference.pptx MANUAL.txt docx/_rels/.rels pptx/_rels/.rels abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml)'+ '--print-default-data-file[Data file to print]:FILE:(MANUAL.txt abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/_rels/.rels docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/_rels/.rels pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml reference.docx reference.odt reference.pptx templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml)' '--print-highlight-style[Highlighting style]:STYLE:(pygments tango espresso zenburn kate monochrome breezedark haddock)' '-v[Print version]' '--version[Print version]'@@ -371,7 +371,7 @@ complete -c pandoc -l list-highlight-languages -d "List highlighting languages" complete -c pandoc -l list-highlight-styles -d "List highlighting styles" complete -c pandoc -s D -l print-default-template -d "Format to print template for" -r -a "ansi asciidoc asciidoc_legacy asciidoctor bbcode bbcode_fluxbb bbcode_hubzilla bbcode_phpbb bbcode_steam bbcode_xenforo beamer biblatex bibtex chunkedhtml commonmark commonmark_x context csljson djot docbook docbook4 docbook5 docx dokuwiki dzslides epub epub2 epub3 fb2 gfm haddock html html4 html5 icml ipynb jats jats_archiving jats_articleauthoring jats_publishing jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict markua mediawiki ms muse native odt opendocument opml org pdf plain pptx revealjs rst rtf s5 slideous slidy t2t tei texinfo textile typst vimdoc xml xwiki zimwiki"-complete -c pandoc -l print-default-data-file -d "Data file to print" -r -a "reference.docx reference.odt reference.pptx MANUAL.txt docx/_rels/.rels pptx/_rels/.rels abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml"+complete -c pandoc -l print-default-data-file -d "Data file to print" -r -a "MANUAL.txt abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/_rels/.rels docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/_rels/.rels pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml reference.docx reference.odt reference.pptx templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml" complete -c pandoc -l print-highlight-style -d "Highlighting style" -r -a "pygments tango espresso zenburn kate monochrome breezedark haddock" complete -c pandoc -s v -l version -d "Print version" complete -c pandoc -s h -l help -d "Show help"
@@ -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"+ ]+]+```
@@ -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+```
@@ -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+```
@@ -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+```
@@ -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.+```
@@ -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} ::: ::::: ```
@@ -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). ```
@@ -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" ]+ ]+]+```
@@ -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. ```
@@ -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
@@ -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 "."+ ]+ ]
@@ -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."+ ]+ ]+ ]+]
binary file changed (10692 → 10689 bytes)
binary file changed (10824 → 10824 bytes)
binary file changed (absent → 10675 bytes)
binary file changed (11236 → 11233 bytes)
binary file changed (10655 → 10776 bytes)
binary file changed (27383 → 27434 bytes)
binary file changed (10842 → 10853 bytes)
binary file changed (11034 → 11048 bytes)
binary file changed (10702 → 10677 bytes)
binary file changed (10685 → 10670 bytes)
binary file changed (10918 → 10903 bytes)
binary file changed (10699 → 10686 bytes)
binary file changed (10845 → 10874 bytes)
binary file changed (10615 → 10625 bytes)
binary file changed (10946 → 10951 bytes)
binary file changed (10882 → 10876 bytes)
binary file changed (10870 → 10887 bytes)
binary file changed (10783 → 10773 bytes)
binary file changed (10617 → 10591 bytes)
binary file changed (absent → 15874 bytes)
@@ -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."+ ]+ ]
@@ -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"
@@ -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 "." ]-]+ ]
@@ -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" ] ]
@@ -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>
@@ -1069,7 +1069,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Plain+ [ Para [ Str "c" , SoftBreak , Str "c"
@@ -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" ] ] ] ] ]
@@ -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 []]]
@@ -1458,7 +1458,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Plain+ [ Para [ Str "c" , SoftBreak , Str "c"
@@ -9,6 +9,7 @@ import GHC.IO.Encoding import Test.Tasty import qualified Tests.Command+import qualified Tests.ImageSize import qualified Tests.Old import qualified Tests.Readers.Creole import qualified Tests.Readers.Docx@@ -65,6 +66,7 @@ , testGroup "Shared" Tests.Shared.tests , testGroup "MediaBag" Tests.MediaBag.tests , testGroup "XML" Tests.XML.tests+ , testGroup "ImageSize" Tests.ImageSize.tests , testGroup "Writers" [ testGroup "Native" Tests.Writers.Native.tests , testGroup "ConTeXt" Tests.Writers.ConTeXt.tests
@@ -88,49 +88,49 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Math InlineMath "F_{1}" ] ]+ [ Plain [ Math InlineMath "F_{1}" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Math InlineMath "F_{2}" ] ]+ [ Plain [ Math InlineMath "F_{2}" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Math InlineMath "F_{3}" ] ]+ [ Plain [ Math InlineMath "F_{3}" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Math InlineMath "F_{4}" ] ]+ [ Plain [ Math InlineMath "F_{4}" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Math InlineMath "F_{5}" ] ]+ [ Plain [ Math InlineMath "F_{5}" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Math InlineMath "F_{6}" ] ]+ [ Plain [ Math InlineMath "F_{6}" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Math InlineMath "F_{7}" ] ]+ [ Plain [ Math InlineMath "F_{7}" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Math InlineMath "F_{8}" ] ]+ [ Plain [ Math InlineMath "F_{8}" ] ] ] , Row ( "" , [] , [] )@@ -139,49 +139,49 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Str "1" ] ]+ [ Plain [ Str "1" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Str "1" ] ]+ [ Plain [ Str "1" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Str "2" ] ]+ [ Plain [ Str "2" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Str "3" ] ]+ [ Plain [ Str "3" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Str "5" ] ]+ [ Plain [ Str "5" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Str "8" ] ]+ [ Plain [ Str "8" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Str "13" ] ]+ [ Plain [ Str "13" ] ] , Cell ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1)- [ Para [ Str "21" ] ]+ [ Plain [ Str "21" ] ] ] ] ]@@ -204,11 +204,11 @@ , Space , Str "Undergrads" ]- , SoftBreak ] ( "https://github.com/johanvx/typst-undergradmath" , "" )+ , SoftBreak ] ] , Para@@ -294,7 +294,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Str "2023-05-22"@@ -308,7 +308,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Str "This" , Space , Str "is"@@ -349,7 +349,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Str "\128166" ] ]@@ -359,7 +359,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Str "Get" , Space , Str "this"@@ -404,7 +404,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Str "No"@@ -420,7 +420,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Str "Don\8217t" , Space , Str "know"@@ -558,7 +558,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "x^{2}"@@ -572,7 +572,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\sqrt{2}"@@ -595,7 +595,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "x_{i,j}"@@ -609,7 +609,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\frac{2}{3}"@@ -683,7 +683,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\alpha"@@ -697,7 +697,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\xi"@@ -720,7 +720,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\beta"@@ -734,7 +734,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\959"@@ -751,7 +751,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\gamma"@@ -771,7 +771,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\pi"@@ -794,7 +794,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\delta"@@ -814,7 +814,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\varpi"@@ -831,7 +831,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\epsilon"@@ -845,7 +845,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\rho"@@ -862,7 +862,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\varepsilon"@@ -876,7 +876,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\varrho"@@ -893,7 +893,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\zeta"@@ -907,7 +907,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\sigma"@@ -930,7 +930,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\eta"@@ -944,7 +944,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\varsigma"@@ -966,7 +966,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\theta"@@ -986,7 +986,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\tau"@@ -1003,7 +1003,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\vartheta"@@ -1017,7 +1017,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\upsilon"@@ -1040,7 +1040,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\iota"@@ -1054,7 +1054,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\phi"@@ -1077,7 +1077,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\kappa"@@ -1091,7 +1091,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\varphi"@@ -1108,7 +1108,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\lambda"@@ -1128,7 +1128,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\chi"@@ -1145,7 +1145,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\mu"@@ -1159,7 +1159,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\psi"@@ -1182,7 +1182,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\nu"@@ -1196,7 +1196,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\omega"@@ -1246,7 +1246,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\cup"@@ -1260,7 +1260,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\mathbb{R}"@@ -1277,7 +1277,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\forall"@@ -1294,7 +1294,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\cap"@@ -1308,7 +1308,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\mathbb{Z}"@@ -1325,7 +1325,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\exists"@@ -1342,7 +1342,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\subset"@@ -1356,7 +1356,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\mathbb{Q}"@@ -1373,7 +1373,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\neg"@@ -1390,7 +1390,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\subseteq"@@ -1404,7 +1404,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\mathbb{N}"@@ -1421,7 +1421,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\vee"@@ -1438,7 +1438,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\supset"@@ -1452,7 +1452,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\mathbb{C}"@@ -1469,7 +1469,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\land"@@ -1486,7 +1486,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\supseteq"@@ -1500,7 +1500,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\varnothing"@@ -1514,7 +1514,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\vdash"@@ -1531,7 +1531,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\in"@@ -1545,7 +1545,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\varnothing"@@ -1559,7 +1559,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\models"@@ -1576,7 +1576,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\notin"@@ -1590,7 +1590,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\1488"@@ -1604,7 +1604,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\smallsetminus"@@ -1814,7 +1814,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "f'"@@ -1831,7 +1831,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\dot{a}"@@ -1845,10 +1845,10 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] )- [ Math InlineMath "\\widetilde{a}"+ [ Math InlineMath "\\tilde{a}" , Str "\8192" , Code ( "" , [] , [] ) "tilde(a)" ]@@ -1862,7 +1862,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "f''"@@ -1877,7 +1877,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\ddot{a}"@@ -1891,7 +1891,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\overline{a}"@@ -1908,7 +1908,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\Sigma^{\\ast}"@@ -1922,7 +1922,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\hat{a}"@@ -1936,7 +1936,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math@@ -2181,7 +2181,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\sin"@@ -2195,7 +2195,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\sinh"@@ -2209,7 +2209,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\arcsin"@@ -2226,7 +2226,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\cos"@@ -2240,7 +2240,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\cosh"@@ -2254,7 +2254,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\arccos"@@ -2271,7 +2271,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\tan"@@ -2285,7 +2285,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\tanh"@@ -2299,7 +2299,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\arctan"@@ -2316,7 +2316,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\sec"@@ -2330,7 +2330,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\coth"@@ -2344,7 +2344,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\min"@@ -2361,7 +2361,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\csc"@@ -2375,7 +2375,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\det"@@ -2389,7 +2389,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\max"@@ -2406,7 +2406,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\cot"@@ -2420,7 +2420,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\dim"@@ -2434,7 +2434,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\inf"@@ -2451,7 +2451,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\exp"@@ -2465,7 +2465,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\ker"@@ -2479,7 +2479,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\sup"@@ -2496,7 +2496,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\log"@@ -2510,7 +2510,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\deg"@@ -2524,7 +2524,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\liminf"@@ -2541,7 +2541,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\ln"@@ -2555,7 +2555,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\arg"@@ -2569,7 +2569,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\limsup"@@ -2586,7 +2586,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\lg"@@ -2600,7 +2600,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\gcd"@@ -2614,7 +2614,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\lim"@@ -2651,7 +2651,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "<"@@ -2668,7 +2668,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\angle"@@ -2682,7 +2682,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\cdot"@@ -2699,7 +2699,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\leq"@@ -2716,7 +2716,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\measuredangle"@@ -2730,7 +2730,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\pm"@@ -2747,7 +2747,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath ">"@@ -2764,7 +2764,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\ell"@@ -2778,7 +2778,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\mp"@@ -2795,7 +2795,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\geq"@@ -2812,7 +2812,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\parallel"@@ -2826,7 +2826,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\times"@@ -2843,7 +2843,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\neq"@@ -2860,7 +2860,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "45{^\\circ}"@@ -2874,7 +2874,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\div"@@ -2891,7 +2891,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\ll"@@ -2908,7 +2908,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\cong"@@ -2922,7 +2922,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\ast"@@ -2942,7 +2942,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\gg"@@ -2959,7 +2959,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\ncong"@@ -2974,7 +2974,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\mid"@@ -2991,7 +2991,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\approx"@@ -3005,7 +3005,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\sim"@@ -3019,7 +3019,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\nmid"@@ -3036,7 +3036,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\asymp"@@ -3055,7 +3055,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\simeq"@@ -3069,7 +3069,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "n!"@@ -3086,7 +3086,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\equiv"@@ -3100,7 +3100,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\nsim"@@ -3114,7 +3114,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\partial"@@ -3131,7 +3131,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\prec"@@ -3145,7 +3145,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\oplus"@@ -3159,7 +3159,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\nabla"@@ -3176,7 +3176,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\preceq"@@ -3190,7 +3190,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\ominus"@@ -3204,7 +3204,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\295"@@ -3221,7 +3221,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\succ"@@ -3235,7 +3235,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\odot"@@ -3249,7 +3249,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\circ"@@ -3268,7 +3268,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\succeq"@@ -3282,7 +3282,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\otimes"@@ -3296,7 +3296,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\star"@@ -3313,7 +3313,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\propto"@@ -3327,7 +3327,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\oslash"@@ -3346,7 +3346,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\sqrt{}"@@ -3363,7 +3363,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\doteq"@@ -3382,7 +3382,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\upharpoonright"@@ -3396,7 +3396,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\10003"@@ -3483,7 +3483,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\rightarrow"@@ -3500,7 +3500,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\mapsto"@@ -3520,7 +3520,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\nrightarrow"@@ -3534,7 +3534,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\longmapsto"@@ -3553,7 +3553,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\longrightarrow"@@ -3567,7 +3567,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\leftarrow"@@ -3587,7 +3587,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\Rightarrow"@@ -3605,7 +3605,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math@@ -3627,7 +3627,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\nRightarrow"@@ -3643,7 +3643,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\downarrow"@@ -3660,7 +3660,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\Longrightarrow"@@ -3676,7 +3676,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\uparrow"@@ -3693,7 +3693,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\rightsquigarrow"@@ -3708,7 +3708,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\updownarrow"@@ -3837,7 +3837,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\int"@@ -3851,7 +3851,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\iiint"@@ -3866,7 +3866,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\bigcup"@@ -3883,7 +3883,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\iint"@@ -3898,7 +3898,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\oint"@@ -3913,7 +3913,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\bigcap"@@ -3949,7 +3949,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "()"@@ -3963,7 +3963,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\langle\\rangle"@@ -3979,7 +3979,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math@@ -3997,7 +3997,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math InlineMath "\\lbrack\\rbrack"@@ -4011,7 +4011,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math@@ -4027,7 +4027,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math@@ -4046,7 +4046,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math@@ -4061,7 +4061,7 @@ AlignDefault (RowSpan 1) (ColSpan 1)- [ Para+ [ Plain [ Span ( "" , [ "box" ] , [] ) [ Math@@ -5345,5 +5345,216 @@ , Space , Str "2023-05-22" ]+ ]+ , Header 1 ( "" , [] , [] ) [ Str "Citations" ]+ , Para+ [ Str "Normal:"+ , Space+ , Cite+ [ Citation+ { citationId = "brown01"+ , citationPrefix = []+ , citationSuffix = []+ , citationMode = NormalCitation+ , citationNoteNum = 0+ , citationHash = 0+ }+ ]+ [ Str "[brown01]" ]+ , Space+ , Str "or"+ , Space+ , Cite+ [ Citation+ { citationId = "brown01"+ , citationPrefix = []+ , citationSuffix = []+ , citationMode = NormalCitation+ , citationNoteNum = 0+ , citationHash = 0+ }+ ]+ [ Str "[brown01]" ]+ , Str "."+ ]+ , Para+ [ Str "Prose:"+ , Space+ , Cite+ [ Citation+ { citationId = "brown01"+ , citationPrefix = []+ , citationSuffix = []+ , citationMode = AuthorInText+ , citationNoteNum = 0+ , citationHash = 0+ }+ ]+ [ Str "[brown01]" ]+ ]+ , Para+ [ Str "Year:"+ , Space+ , Cite+ [ Citation+ { citationId = "brown01"+ , citationPrefix = []+ , citationSuffix = []+ , citationMode = SuppressAuthor+ , citationNoteNum = 0+ , citationHash = 0+ }+ ]+ [ Str "[brown01]" ]+ ]+ , Para+ [ Str "Author:"+ , Space+ , Cite+ [ Citation+ { citationId = "brown01"+ , citationPrefix = []+ , citationSuffix = []+ , citationMode = NormalCitation+ , citationNoteNum = 0+ , citationHash = 0+ }+ ]+ [ Str "[brown01]" ]+ ]+ , Header+ 1+ ( "" , [] , [] )+ [ Str "Inline"+ , Space+ , Str "elements"+ , Space+ , Str "across"+ , Space+ , Str "paragraphs"+ ]+ , Para [ Emph [ Str "hi" ] ]+ , Para [ Emph [ Str "there" ] ]+ , Para [ Str "Hello" , Space , Strong [ Str "again" ] ]+ , Para+ [ Strong [ Str "and" , Space , Str "again" ]+ , Space+ , Str "world."+ ]+ , Para [ Underline [ Str "Para" , Space , Str "one." ] ]+ , Para [ Underline [ Str "Para" , Space , Str "two." ] ]+ , Header+ 2+ ( "" , [] , [] )+ [ SmallCaps [ Str "smallcaps" , Space , Str "heading" ] ]+ , Header+ 1+ ( "" , [] , [] )+ [ Str "More"+ , Space+ , Str "inline"+ , Space+ , Str "elements"+ , Space+ , Str "across"+ , Space+ , Str "paragraphs"+ ]+ , Para+ [ Link+ ( "" , [] , [] )+ [ Str "link" , Space , Str "one" ]+ ( "https://example.com/typst" , "" )+ ]+ , Para+ [ Link+ ( "" , [] , [] )+ [ Str "link" , Space , Str "two" ]+ ( "https://example.com/typst" , "" )+ ]+ , Para [ Emph [ Str "hi" , Space ] ]+ , Para [ Emph [ Str "struck" , Space , Str "middle" ] ]+ , Para [ Emph [ Space , Str "there" ] ]+ , Para [ Str "mixed" , Space , Str "case" ]+ , Para [ Str "also" , Space , Str "here" ]+ , DefinitionList+ [ ( [ Str "term" ]+ , [ [ Para [ Emph [ Str "term" , Space , Str "one" ] ]+ , Para [ Emph [ Str "term" , Space , Str "two" ] ]+ ]+ ]+ )+ ]+ , Para [ Str "Edge" , Space , Str "case" ]+ , Para+ [ Emph [ Str "split" , Space , Str "here" ]+ , Space+ , Str "continues."+ ]+ , Header+ 1+ ( "" , [] , [] )+ [ Str "Splitting"+ , Space+ , Str "edge"+ , Space+ , Str "cases"+ ]+ , Para [ Span ( "edge-label" , [] , [] ) [] ]+ , Para+ [ Str "Trailing"+ , Space+ , Str "edge"+ , Space+ , Emph [ Str "splits" ]+ ]+ , Para [ Emph [ Str "here" ] , Space , Str "continues." ]+ , Para+ [ Str "Consecutive"+ , Space+ , Str "breaks"+ , Space+ , Emph [ Str "a" ]+ ]+ , Para [ Emph [ Str "b" ] , Space , Str "end." ]+ , Para [ Str "Vanishing" ]+ , Para [ Str "body." ]+ , Para+ [ Emph [ Underline [ Str "nested" , Space , Str "one" ] ] ]+ , Para+ [ Emph [ Underline [ Str "nested" , Space , Str "two" ] ] ]+ , Para [ Emph [ Str "a" , Space ] ]+ , Table+ ( "" , [] , [] )+ (Caption Nothing [])+ [ ( AlignDefault , ColWidthDefault ) ]+ (TableHead ( "" , [] , [] ) [])+ [ TableBody+ ( "" , [] , [] )+ (RowHeadColumns 0)+ []+ [ Row+ ( "" , [] , [] )+ [ Cell+ ( "" , [] , [] )+ AlignDefault+ (RowSpan 1)+ (ColSpan 1)+ [ Plain [ Str "cell" ] ]+ ]+ ]+ ]+ (TableFoot ( "" , [] , [] ) [])+ , Para [ Emph [ Space , Str "c" ] ]+ , Para+ [ Str "Ref"+ , Space+ , Str "supplement:"+ , Space+ , Link+ ( "" , [ "ref" ] , [] )+ [ Str "see" , Space , Str "this" , Space , Str "section" ]+ ( "#edge-label" , "" )+ , Str "." ] ]
@@ -26,3 +26,75 @@ #include "undergradmath.typ" ++= Citations++Normal: #cite(<brown01>) or @brown01.++Prose: #cite(<brown01>, form: "prose")++Year: #cite(<brown01>, form: "year")++Author: #cite(<brown01>, form: "author")+++= Inline elements across paragraphs++#emph[hi++there]++Hello #strong[again++and again] world.++#underline[Para one.++Para two.]++#smallcaps[#heading(level: 2)[smallcaps heading]]+++= More inline elements across paragraphs++#link("https://example.com/typst")[link one++link two]++#emph[hi #block[struck middle] there]++#lower[Mixed Case++Also Here]++/ term: #emph[term one++ term two]++Edge case #emph[++split here] continues.+++= Splitting edge cases <edge-label>++Trailing edge #emph[splits++here] continues.++Consecutive breaks #emph[a+++b] end.++Vanishing #emph[++] body.++#emph[#underline[nested one++nested two]]++#emph[a #grid(columns: 1)[cell] c]++Ref supplement: @edge-label[see this section].
@@ -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
@@ -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 {
@@ -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 {
@@ -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.
@@ -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 > 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: \$ \\ \> \[ \{ </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 @@ <hr /> </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: <http://example.com/> </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>
@@ -542,7 +542,6 @@ #block[ #block[ foo- ] ] #block[@@ -557,7 +556,6 @@ #block[ foo- ] This should be a code block, though:
@@ -43,7 +43,10 @@ import qualified Control.Exception as E import qualified Text.XML as Conduit-import Text.XML.Unresolved (InvalidEventStream(..))+import qualified Text.XML.Stream.Parse as P+import Data.Conduit (runConduit, (.|))+import qualified Data.Conduit.List as CL+import Data.Char (isSpace) import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.Map as M@@ -64,46 +67,137 @@ elementToElement . Conduit.documentRoot <$> either (Left . T.pack . E.displayException) Right (Conduit.parseText Conduit.def{ Conduit.psRetainNamespaces = True- , Conduit.psDecodeEntities = decodeEnts } t)- where- decodeEnts ref = case M.lookup ref entityMap of- Nothing -> XML.ContentEntity ref- Just t' -> XML.ContentText t'+ , Conduit.psDecodeEntities =+ entityResolver entityMap } t) parseXMLContents :: TL.Text -> Either T.Text [Content] parseXMLContents = parseXMLContentsWithEntities mempty +-- | Parse a list of XML contents. Unlike 'parseXMLElementWithEntities',+-- this does not require a single root element: multiple sibling+-- elements and top-level text are accepted. An XML declaration,+-- DOCTYPE, comments, and processing instructions are skipped. parseXMLContentsWithEntities :: M.Map T.Text T.Text -> TL.Text -> Either T.Text [Content] parseXMLContentsWithEntities entityMap t =- case Conduit.parseText Conduit.def{ Conduit.psRetainNamespaces = True- , Conduit.psDecodeEntities = decodeEnts- } t of- Left e ->- case E.fromException e of- Just (ContentAfterRoot _) ->- elContent <$> parseXMLElementWithEntities entityMap- ("<wrapper>" <> t <> "</wrapper>")- _ -> Left . T.pack . E.displayException $ e- Right x -> Right [Elem . elementToElement . Conduit.documentRoot $ x]+ case runConduit (CL.sourceList (TL.toChunks t) .| parser+ .| CL.fold step (Right ([], []))) of+ Left err -> Left . T.pack . E.displayException $ (err :: E.SomeException)+ Right st -> normalizeTop <$> finishContents st where- decodeEnts ref = case M.lookup ref entityMap of- Nothing -> XML.ContentEntity ref- Just t' -> XML.ContentText t'+ parser = P.parseTextPos P.def{ P.psRetainNamespaces = True+ , P.psDecodeEntities =+ entityResolver entityMap } +entityResolver :: M.Map T.Text T.Text -> T.Text -> XML.Content+entityResolver entityMap ref =+ case M.lookup ref entityMap of+ Nothing -> XML.ContentEntity ref+ Just t' -> XML.ContentText t'++-- An element that is being built, with its children so far in reverse order.+data Frame = Frame QName [Attr] [Content]++-- The top-level contents built so far (reversed) and the stack of+-- open elements (innermost first); or an error.+type BuildState = Either T.Text ([Content], [Frame])++-- Fold one parse event into the content forest being built, checking+-- that start and end tags are balanced.+step :: BuildState -> P.EventPos -> BuildState+step st@(Left _) _ = st+step (Right (cs, stack)) (pos, event) =+ case event of+ XML.EventBeginElement name attribs ->+ case mapM toAttr attribs of+ Left e -> Left e+ Right attrs -> Right (cs, Frame (nameToQName name) attrs [] : stack)+ XML.EventEndElement name ->+ case stack of+ Frame fname attrs children : stack'+ | nameToQName name == fname ->+ emit (Elem (Element fname attrs (mergeText (reverse children))+ Nothing)) stack'+ _ -> Left $ atPos $ "Unexpected close tag " <>+ showQ (nameToQName name)+ XML.EventContent c ->+ case contentToText c of+ Left e -> Left e+ Right txt -> emit (textContent txt) stack+ XML.EventCDATA txt -> emit (textContent txt) stack+ _ -> Right (cs, stack)+ -- skip begin/end document, doctype, comments, and PIs+ where+ -- add finished content to the enclosing element (or the top level)+ emit c [] = Right (c : cs, [])+ emit c (Frame name attrs children : stack') =+ Right (cs, Frame name attrs (c : children) : stack')++ textContent txt = Text (CData CDataText txt Nothing)++ toAttr (name, vals) =+ Attr (nameToQName name) . T.concat <$> mapM contentToText vals++ contentToText (XML.ContentText txt) = Right txt+ contentToText (XML.ContentEntity ref) =+ Left $ atPos $ "Unresolved entity &" <> ref <> ";"++ atPos msg = case pos of+ Just pr -> T.pack (show pr) <> ": " <> msg+ Nothing -> msg++finishContents :: BuildState -> Either T.Text [Content]+finishContents (Left e) = Left e+finishContents (Right (cs, [])) = Right (mergeText (reverse cs))+finishContents (Right (_, Frame name _ _ : _)) =+ Left $ "Missing close tag for " <> showQ name++showQ :: QName -> T.Text+showQ (QName name _ Nothing) = name+showQ (QName name _ (Just pre)) = pre <> ":" <> name++-- Merge adjacent text nodes (the stream parser splits text at+-- entity and CDATA boundaries).+mergeText :: [Content] -> [Content]+mergeText (Text cd : cs) =+ case span isText cs of+ ([], _) -> Text cd : mergeText cs+ (ts, rest) -> Text cd{ cdData = T.concat (cdData cd :+ [d | Text (CData _ d _) <- ts]) }+ : mergeText rest+ where+ isText Text{} = True+ isText _ = False+mergeText (c : cs) = c : mergeText cs+mergeText [] = []++-- If the content is a single element surrounded only by whitespace+-- (as in a complete XML document, where there may be newlines after+-- an XML declaration or DOCTYPE), drop the whitespace.+normalizeTop :: [Content] -> [Content]+normalizeTop cs =+ case filter (not . isWhitespaceText) cs of+ cs'@[Elem _] -> cs'+ _ -> cs+ where+ isWhitespaceText (Text cd) = T.all isSpace (cdData cd)+ isWhitespaceText _ = False++nameToQName :: Conduit.Name -> QName+nameToQName (Conduit.Name localName mbns mbpref) =+ case mbpref of+ Nothing ->+ case T.stripPrefix "xmlns:" localName of+ Just rest -> QName rest mbns (Just "xmlns")+ Nothing -> QName localName mbns mbpref+ _ -> QName localName mbns mbpref+ elementToElement :: Conduit.Element -> Element elementToElement (Conduit.Element name attribMap nodes) =- Element (nameToQname name) attrs (mapMaybe nodeToContent nodes) Nothing+ Element (nameToQName name) attrs (mapMaybe nodeToContent nodes) Nothing where- attrs = map (\(n,v) -> Attr (nameToQname n) v) $+ attrs = map (\(n,v) -> Attr (nameToQName n) v) $ M.toList attribMap- nameToQname (Conduit.Name localName mbns mbpref) =- case mbpref of- Nothing ->- case T.stripPrefix "xmlns:" localName of- Just rest -> QName rest mbns (Just "xmlns")- Nothing -> QName localName mbns mbpref- _ -> QName localName mbns mbpref nodeToContent :: Conduit.Node -> Maybe Content nodeToContent (Conduit.NodeElement el) =
@@ -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 "<"- '>' -> fromText ">"- '&' -> fromText "&"- '"' -> fromText """+ '<' -> text "<"+ '>' -> text ">"+ '&' -> text "&"+ '"' -> text """ -- we use ' instead of ' because IE apparently has difficulties -- rendering ' in xhtml. -- Reported by Rohan Drape <rohan.drape@gmail.com>.- '\'' -> fromText "'"- _ -> singleton c+ '\'' -> text "'"+ _ -> 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)