git.iko.soy/typeable/pandoc
on 6fbdb48 help sign in
14 files changed+1599−1248
since splitsnapshots
unifiedsplit
.circleci/config.yml+2−2 cabal.project+1−1 changelog.md+182−98 doc/libraries.md+59 pandoc.cabal+5 src/Text/Pandoc/Writers/LaTeX.hs+15−551 src/Text/Pandoc/Writers/LaTeX/Citation.hs+181 src/Text/Pandoc/Writers/LaTeX/Lang.hs+191 src/Text/Pandoc/Writers/LaTeX/Util.hs+274 src/Text/Pandoc/Writers/Markdown.hs+6−591 src/Text/Pandoc/Writers/Markdown/Inline.hs+601 src/Text/Pandoc/Writers/Markdown/Types.hs+81 stack.yaml+1−1 trypandoc/trypandoc.hs−4
.circleci/config.yml+2−2
@@ -20,10 +20,10 @@ jobs:
2020 command: stack --no-terminal setup
2121 - run:
2222 name: Run tests
23− command: stack --no-terminal test --ghc-options=-split-sections
23+ command: stack --no-terminal test
2424 - run:
2525 name: Install executable
26− command: stack --no-terminal install --ghc-options=-split-sections
26+ command: stack --no-terminal install
2727 - run:
2828 name: Create artifacts
2929 command: |
cabal.project+1−1
@@ -3,7 +3,7 @@ packages: pandoc.cabal
33tests: True
44
55package pandoc
6− flags: +embed_data_files -trypandoc
6+ flags: +embed_data_files
77 ghc-options: -j +RTS -A256m -RTS
88
99-- source-repository-package
changelog.md+182−98
@@ -2,6 +2,11 @@
22
33## pandoc 2.12 (UNRELEASED -- PROVISIONAL)
44
5+ * When downloading content from URL arguments, be sensitive to
6+ the character encoding (#5600). We can properly handle UTF-8 and latin1
7+ (ISO-8859-1); for others we raise an error. Fall back to latin1 if
8+ no charset is given in the mime type and UTF-8 decoding fails.
9+
510 * Add new unexported module Text.Pandoc.XML.Light, as well
611 as Text.Pandoc.XML.Light.Types, Text.Pantoc.XML.Light.Proc,
712 Text.Pandoc.XML.Light.Output. (Closes #6001, #6565, #7091).
@@ -34,75 +39,6 @@
3439 these have been added. And the docx golden tests have been updated,
3540 because the new parser does not preserve the order of attributes.
3641
37− * Text.Pandoc.App
38−
39− + Add `parseOptionsFromArgs` [API change, new exported function].
40−
41− * Text.Pandoc.Citeproc.BibTeX
42−
43− + `Text.Pandoc.Citeproc.writeBibTeXString` now returns
44− `Doc Text` instead of `Text` (#7068).
45− + Correctly handle `pages` (= `page` in CSL) (#7067).
46− + Correctly handle BibLaTeX `langid` (= `language` in CSL, #7067).
47− + In BibTeX output, protect foreign titles since there's no language
48− field (#7067).
49− + Clean up BibTeX parsing (#7049). Previously there was a messy code
50− path that gave strange results in some cases, not passing through raw
51− tex but trying to extract a string content. This was an artefact of
52− trying to handle some special bibtex-specific commands in the BibTeX
53− reader. Now we just handle these in the LaTeX reader and simplify
54− parsing in the BibTeX reader. This does mean that more raw tex will
55− be passed through (and currently this is not sensitive to the
56− `raw_tex` extension; this should be fixed).
57−
58− * Text.Pandoc.Citeproc.MetaValue
59−
60− + Correctly parse "raw" date value in markdown references metadata.
61− (See jgm/citeproc#53.)
62−
63− * Text.Pandoc.Class
64−
65− + Add `getTimestamp` [API change]. This attempts to read the
66− `SOURCE_DATE_EPOCH` environment variable and parse a UTC time
67− from it (treating it as a unix date stamp, see
68− https://reproducible-builds.org/specs/source-date-epoch/). If the
69− variable is not set or can't be parsed as a unix date stamp, then the
70− function returns the current date.
71−
72− * Text.Pandoc.Error
73−
74− + Remove unused variables (Albert Krewinkel)
75− + Export `renderError` [API change].
76− + Refactor `handleError` to use `renderError`. This allows us render
77− error messages without exiting.
78−
79− * Text.Pandoc.Extensions
80−
81− + `Ext_task_lists` is now supported by org (and turned
82− on by default) (Albert Krewinkel, #6336).
83− + Remove `Ext_fenced_code_attributes` from allowed commonmark attributes
84− (#7097). This attribute was listed as allowed, but it didn't actually
85− do anything. Use `attributes` for code attributes and more.
86−
87− * Lua subsystem:
88−
89− + Always load built-in Lua scripts from default data-dir (Albert
90− Krewinkel). The Lua modules `pandoc` and `pandoc.List` are now always
91− loaded from the system's default data directory. Loading from a
92− different directory by overriding the default path, e.g. via
93− `--data-dir`, is no longer supported to avoid unexpected behavior
94− and to address security concerns.
95− + Add module "pandoc.path" (Albert Krewinkel, #6001, #6565).
96− The module allows to work with file paths in a convenient and
97− platform-independent manner.
98−
99− * Text.Pandoc.PDF
100−
101− + Disable `smart` extension when building PDF via LaTeX.
102− This is to prevent accidental creation of ligatures like
103− `` ?` `` and `` !` `` (especially in languages with quotations like
104− German), and similar ligature issues. (See jgm/citeproc#54.)
105−
10642 * DocBook reader:
10743
10844 + Avoid expensive tree normalization step, as it is not necessary
@@ -137,6 +73,11 @@
13773
13874 * LaTeX reader:
13975
76+ + Don't export `tokenize`, `untokenize` [API change]. These are internal
77+ implementation details, which were only exported for testing.
78+ They don't belong in the public API.
79+ + Improved efficiency of the parser. With these changes the reader
80+ is almost twice as fast as in the last release in our benchmarks.
14081 + Code cleanup, removing some unnecessary things.
14182 + Rewrite `withRaw` so it doesn't rely on fragile assumptions
14283 about token positions (which break when macros are expanded)
@@ -156,11 +97,19 @@
15697 it doesn't change anything.
15798 + Improve `braced'`. Remove the parameter, have it parse the
15899 opening brace, and make it more efficient.
100+ + Factor out pieces of the LaTeX reader to make the module smaller.
101+ This reduces memory demands when compiling. Created
102+ Text.Pandoc.Readers.{LaTeX,Math,Citation,Table,Macro,Accent}.
103+ Changed Text.Pandoc.Readers.LaTeX.SIunitx to export a command map
104+ instead of individual commands.
105+
106+ * Make Text.Pandoc.Readers.LaTeX.Types an unexported module [API change].
159107
160108 * Markdown reader:
161109
162110 + Improved handling of mmd link attributes in references (#7080).
163111 Previously they only worked for links that had titles.
112+ + Improved efficiency of the parser (benchmarks show a 15% speedup).
164113
165114 * OPML reader:
166115
@@ -218,7 +167,7 @@
218167 + Use `getTimestamp` instead of `getCurrentTime` for timestamp.
219168 Setting `SOURCE_DATE_EPOCH` will allow reproducible builds.
220169
221− * Text.Pandoc.Writers.EPUB
170+ * EPUB writer:
222171
223172 + Use `getTimestamp` instead of `getCurrentTime` for timestamp.
224173 Setting `SOURCE_DATE_EPOCH` will allow reproducible builds (#7093).
@@ -233,6 +182,10 @@
233182 contains an ampersand or another character with a special meaning
234183 in XML.
235184
185+ * Jira writer:
186+
187+ + Use Span identifiers as anchors (Albert Krewinkel,
188+
236189 * LaTeX writer:
237190
238191 + Adjust hypertargets to beginnings of paragraphs (#7078).
@@ -241,6 +194,8 @@
241194 This makes a particular difference for links to citations using
242195 `--citeproc` and `link-citations: true`.
243196 + Change BCP47 lang tag from `jp` to `ja` (Mauro Bieg, #7047).
197+ + Use function instead of map for accent lookup (should be
198+ more efficient).
244199
245200 * Markdown writer:
246201
@@ -279,40 +234,151 @@
279234 `--include-in-header`, `--include-before-body`, `--include-after-body`)
280235 may be used.
281236
282− * LaTeX template: Update to iftex package (#7073) (Andrew Dunning)
237+ * LaTeX template:
238+
239+ + Update to iftex package (#7073) (Andrew Dunning)
240+ + Wrap url colours in braces (#7121) (Loïc Grobol).
283241
284242 * revealjs template: Add 'center' option for vertical slide centering.
285243 (maurerle, #7104).
286244
287245 * Text.Pandoc.XML: Improve efficiency of `fromEntities`.
288246
289− * Test suite: a more robust way of testing the executable.
290− Many of our tests require running the pandoc executable. This is
291− problematic for a few different reasons. First, cabal-install will
292− sometimes run the test suite after building the library but before
293− building the executable, which means the executable isn't in place for
294− the tests. One can work around that by first building, then building and
295− running the tests, but that's fragile. Second, we have to find the
296− executable. So far, we've done that using a function `findPandoc` that
297− attempts to locate it relative to the test executable (which can be
298− located using findExecutablePath). But the logic here is delicate and
299− work with every combination of options. To solve both problems, we add
300− an `--emulate` option to the `test-pandoc` executable. When `--emulate`
301− occurs as the first argument passed to `test-pandoc`, the program simply
302− emulates the regular pandoc executable, using the rest of the arguments
303− (after `--emulate`). Thus, `test-pandoc --emulate -f markdown -t latex`
304− is just like `pandoc -f markdown -t latex`.
305− Since all the work is done by library functions, implementing this
306− emulation just takes a couple lines of code and should be entirely
307− reliable. With this change, we can test the pandoc executable by running
308− the test program itself (locatable using `findExecutablePath`) with the
309− `--emulate` option. This removes the need for the fragile `findPandoc`
310− step, and it means we can run our integration tests even when we're just
311− building the library, not the executable. [Note: part of this change
312− involved simplifying some complex handling to set environment variables
313− for dynamic library paths. I have tested a build with
314− `--enable-dynamic-executable`, and it works, but further testing may be
315− needed.]
247+ * Text.Pandoc.MIME
248+
249+ + Add exported function `getCharset` [API change].
250+
251+ * Text.Pandoc.UTF8: change IO functions to return Text, not String
252+ [API change]. This affects `readFile`, `getContents`, `writeFileWith`,
253+ `writeFile`, `putStrWith`, `putStr`, `putStrLnWith`, `putStrLn`.
254+ `hPutStrWith`, `hPutStr`, `hPutStrLnWith`, `hPutStrLn`, `hGetContents`.
255+ This avoids the need to uselessly create a linked list of characters
256+ when emiting output.
257+
258+ * Text.Pandoc.App
259+
260+ + Add `parseOptionsFromArgs` [API change, new exported function].
261+
262+ * Text.Pandoc.Citeproc.BibTeX
263+
264+ + `Text.Pandoc.Citeproc.writeBibTeXString` now returns
265+ `Doc Text` instead of `Text` (#7068).
266+ + Correctly handle `pages` (= `page` in CSL) (#7067).
267+ + Correctly handle BibLaTeX `langid` (= `language` in CSL, #7067).
268+ + In BibTeX output, protect foreign titles since there's no language
269+ field (#7067).
270+ + Clean up BibTeX parsing (#7049). Previously there was a messy code
271+ path that gave strange results in some cases, not passing through raw
272+ tex but trying to extract a string content. This was an artefact of
273+ trying to handle some special bibtex-specific commands in the BibTeX
274+ reader. Now we just handle these in the LaTeX reader and simplify
275+ parsing in the BibTeX reader. This does mean that more raw tex will
276+ be passed through (and currently this is not sensitive to the
277+ `raw_tex` extension; this should be fixed).
278+
279+ * Text.Pandoc.Citeproc.MetaValue
280+
281+ + Correctly parse "raw" date value in markdown references metadata.
282+ (See jgm/citeproc#53.)
283+
284+ * Text.Pandoc.Citeproc
285+
286+ + Use https URLs for links (Salim B, #7122).
287+
288+ * Text.Pandoc.Class
289+
290+ + Add `getTimestamp` [API change]. This attempts to read the
291+ `SOURCE_DATE_EPOCH` environment variable and parse a UTC time
292+ from it (treating it as a unix date stamp, see
293+ https://reproducible-builds.org/specs/source-date-epoch/). If the
294+ variable is not set or can't be parsed as a unix date stamp, then the
295+ function returns the current date.
296+
297+ * Text.Pandoc.Error
298+
299+ + Add `PandocUnsupportedCharsetError` constructor for
300+ `PandocError` [API change].
301+ + Export `renderError` [API change].
302+ + Refactor `handleError` to use `renderError`. This allows us render
303+ error messages without exiting.
304+ + Remove unused variables (Albert Krewinkel)
305+
306+ * Text.Pandoc.Extensions
307+
308+ + `Ext_task_lists` is now supported by org (and turned
309+ on by default) (Albert Krewinkel, #6336).
310+ + Remove `Ext_fenced_code_attributes` from allowed commonmark attributes
311+ (#7097). This attribute was listed as allowed, but it didn't actually
312+ do anything. Use `attributes` for code attributes and more.
313+
314+ * Lua subsystem:
315+
316+ + Always load built-in Lua scripts from default data-dir (Albert
317+ Krewinkel). The Lua modules `pandoc` and `pandoc.List` are now always
318+ loaded from the system's default data directory. Loading from a
319+ different directory by overriding the default path, e.g. via
320+ `--data-dir`, is no longer supported to avoid unexpected behavior
321+ and to address security concerns.
322+ + Add module "pandoc.path" (Albert Krewinkel, #6001, #6565).
323+ The module allows to work with file paths in a convenient and
324+ platform-independent manner.
325+ + Use strict evaluation when retrieving AST value from the stack
326+ (Albert Krewinkel, #6674).
327+
328+ * Text.Pandoc.PDF
329+
330+ + Disable `smart` extension when building PDF via LaTeX.
331+ This is to prevent accidental creation of ligatures like
332+ `` ?` `` and `` !` `` (especially in languages with quotations like
333+ German), and similar ligature issues. (See jgm/citeproc#54.)
334+
335+ * Text.Pandoc.CSV:
336+
337+ + Fix parsing of unquoted values (#7112). Previously we didn't allow
338+ unescaped quotes in unquoted values, but they are allowed
339+ in CSV.
340+
341+ * Test suite:
342+
343+ + Use a more robust method for testing the executable. Many
344+ of our tests require running the pandoc executable. This
345+ is problematic for a few different reasons. First,
346+ cabal-install will sometimes run the test suite after
347+ building the library but before building the executable,
348+ which means the executable isn't in place for the tests.
349+ One can work around that by first building, then building
350+ and running the tests, but that's fragile. Second, we
351+ have to find the executable. So far, we've done that using
352+ a function `findPandoc` that attempts to locate it
353+ relative to the test executable (which can be located
354+ using findExecutablePath). But the logic here is delicate
355+ and work with every combination of options. To solve both
356+ problems, we add an `--emulate` option to the
357+ `test-pandoc` executable. When `--emulate` occurs as the
358+ first argument passed to `test-pandoc`, the program simply
359+ emulates the regular pandoc executable, using the rest of
360+ the arguments (after `--emulate`). Thus, `test-pandoc
361+ --emulate -f markdown -t latex` is just like `pandoc -f
362+ markdown -t latex`. Since all the work is done by library
363+ functions, implementing this emulation just takes a couple
364+ lines of code and should be entirely reliable. With this
365+ change, we can test the pandoc executable by running the
366+ test program itself (locatable using `findExecutablePath`)
367+ with the `--emulate` option. This removes the need for the
368+ fragile `findPandoc` step, and it means we can run our
369+ integration tests even when we're just building the
370+ library, not the executable. [Note: part of this change
371+ involved simplifying some complex handling to set
372+ environment variables for dynamic library paths. I have
373+ tested a build with `--enable-dynamic-executable`, and it
374+ works, but further testing may be needed.]
375+ + Print accurate location if a test fails (Albert
376+ Krewinkel). Ensures that tasty-hunit reports the location
377+ of the failing test instead of the location of the helper
378+ `test` function.
379+
380+ * Documentation: Update URLs and use `https` where possible (#7122,
381+ Salim B).
316382
317383 * MANUAL.txt
318384
@@ -328,6 +394,9 @@
328394 * Makefile: in `make bench`, create CSV files for comparison and compare
329395 against previous benchmark run. Add timestamp to CSV filenames.
330396
397+ * cabal.project: don't explicitly set -trypandoc.
398+ If we do, this can't be overridden on the cabal command line.
399+
331400 * doc/lua-filters.md: improve documentation for
332401 `pandoc.mediabag.insert`, `pandoc.mediabag.fetch`,
333402 `directory`, `normalize` (Albert Krewinkel).
@@ -339,6 +408,14 @@
339408 * Require citeproc 0.3.0.7, which correctly titlecases when titles
340409 contain non-ASCII characters.
341410
411+ * Use skylighting 0.10.4. This version of skylighting uses xml-conduit
412+ rather than hxt. This speeds up parsing of XML syntax definitions
413+ fourfold, and removes four packages from pandoc's dependency graph:
414+ hxt-charproperties, hxt-unicode, hxt-regex-xmlschema, hxt.
415+
416+ * Add script `tools/parseTimings.pl` to help pin down which
417+ modules take the most time and memory to compile.
418+
342419 * Avoid unnecessary use of NoImplicitPrelude pragma (#7089) (Albert
343420 Krewinkel)
344421
@@ -353,6 +430,13 @@
353430 + Clean up benchmark code.
354431 + Allow specifying patterns using `-p blah'.
355432
433+ * trypandoc: add 2 second timeout.
434+
435+ * Use `-split-sections` in creating linux release binary.
436+ This reduces executable size significantly (by about 30%).
437+
438+ * Remove `weigh-pandoc`. It's not really useful any more, now that our
439+ regular benchmarks include data on allocation.
356440
357441
358442## pandoc 2.11.4 (2021-01-22)
doc/libraries.mdnew file+59
@@ -0,0 +1,59 @@
1+---
2+title: The pandoc family of libraries
3+author: John MacFarlane
4+---
5+
6+The following Haskell libraries have been developed to support
7+pandoc:
8+
9+[skylighting-core] and [skylighting]
10+: Syntax highlighting engine supporting over 140 languages.
11+
12+[citeproc]
13+: Citation processing using CSL stylesheets.
14+
15+[texmath]
16+: Conversion of math between tex, Word equation, MathML, and GNU eqn.
17+
18+[doclayout]
19+: Combinators for laying out a textual document, with support
20+ for line wrapping, tabular layout, and more.
21+
22+[doctemplates]
23+: Supports pandoc's templates.
24+
25+[commonmark], [commonmark-extensions], and [commonmark-pandoc]
26+: Efficient, standards-compliant parser for commonmark and extensions.
27+
28+[ipynb]
29+: Representation of Jupyter notebooks and conversion to and
30+ from JSON.
31+
32+[zip-archive]
33+: A pure zip file creator and extractor, used by pandoc for
34+ docx, ODT, and EPUB.
35+
36+[rfc5051]
37+: Simple unicode collation (used for citation sorting).
38+
39+[emojis]
40+: Conversion between emoji characters and aliases.
41+
42+[jira-wiki-markup]
43+: Support for parsing Jira wiki syntax.
44+
45+[skylighting]: https://hackage.haskell.org/package/skylighting
46+[skylighting-core]: https://hackage.haskell.org/package/skylighting-core
47+[citeproc]: https://hackage.haskell.org/package/citeproc
48+[texmath]: https://hackage.haskell.org/package/texmath
49+[doclayout]: https://hackage.haskell.org/package/doclayout
50+[doctemplates]: https://hackage.haskell.org/package/doctemplates
51+[commonmark]: https://hackage.haskell.org/package/commonmark
52+[commonmark-extensions]: https://hackage.haskell.org/package/commonmark-extensions
53+[commonmark-pandoc]: https://hackage.haskell.org/package/commonmark-pandoc
54+[ipynb]: https://hackage.haskell.org/package/ipynb
55+[zip-archive]: https://hackage.haskell.org/package/zip-archive
56+[rfc5051]: https://hackage.haskell.org/package/rfc5051
57+[emojis]: https://hackage.haskell.org/package/emojis
58+[jira-wiki-markup]: https://hackage.haskell.org/package/jira-wiki-markup
59+
pandoc.cabal+5
@@ -664,7 +664,12 @@ library
664664 Text.Pandoc.Writers.LaTeX.Caption,
665665 Text.Pandoc.Writers.LaTeX.Notes,
666666 Text.Pandoc.Writers.LaTeX.Table,
667+ Text.Pandoc.Writers.LaTeX.Lang,
667668 Text.Pandoc.Writers.LaTeX.Types,
669+ Text.Pandoc.Writers.LaTeX.Citation,
670+ Text.Pandoc.Writers.LaTeX.Util,
671+ Text.Pandoc.Writers.Markdown.Types,
672+ Text.Pandoc.Writers.Markdown.Inline,
668673 Text.Pandoc.Writers.Roff,
669674 Text.Pandoc.Writers.Powerpoint.Presentation,
670675 Text.Pandoc.Writers.Powerpoint.Output,
src/Text/Pandoc/Writers/LaTeX.hs+15−551
@@ -18,11 +18,9 @@ module Text.Pandoc.Writers.LaTeX (
1818 writeLaTeX
1919 , writeBeamer
2020 ) where
21−import Control.Applicative ((<|>))
2221import Control.Monad.State.Strict
23−import Data.Char (isAlphaNum, isAscii, isDigit, isLetter, isSpace,
24− isPunctuation, ord)
25−import Data.List (foldl', intersperse, nubBy, (\\), uncons)
22+import Data.Char (isDigit)
23+import Data.List (intersperse, nubBy, (\\))
2624import Data.Maybe (catMaybes, fromMaybe, isJust, mapMaybe, isNothing)
2725import qualified Data.Map as M
2826import Data.Text (Text)
@@ -34,7 +32,7 @@ import Text.Pandoc.BCP47 (Lang (..), getLang, renderLang)
3432import Text.Pandoc.Class.PandocMonad (PandocMonad, report, toLang)
3533import Text.Pandoc.Definition
3634import Text.Pandoc.Highlighting (formatLaTeXBlock, formatLaTeXInline, highlight,
37− styleToLaTeX, toListingsLanguage)
35+ styleToLaTeX)
3836import Text.Pandoc.ImageSize
3937import Text.Pandoc.Logging
4038import Text.Pandoc.Options
@@ -44,10 +42,15 @@ import Text.Pandoc.Slides
4442import Text.Pandoc.Walk (query, walk, walkM)
4543import Text.Pandoc.Writers.LaTeX.Caption (getCaption)
4644import Text.Pandoc.Writers.LaTeX.Table (tableToLaTeX)
45+import Text.Pandoc.Writers.LaTeX.Citation (citationsToNatbib,
46+ citationsToBiblatex)
4747import Text.Pandoc.Writers.LaTeX.Types (LW, WriterState (..), startingState)
48+import Text.Pandoc.Writers.LaTeX.Lang (toPolyglossia, toBabel)
49+import Text.Pandoc.Writers.LaTeX.Util (stringToLaTeX, StringContext(..),
50+ toLabel, inCmd,
51+ wrapDiv, hypertarget, labelFor,
52+ getListingsLanguage, mbBraced)
4853import Text.Pandoc.Writers.Shared
49−import Text.Printf (printf)
50−import qualified Data.Text.Normalize as Normalize
5154import qualified Text.Pandoc.Writers.AnnotatedTable as Ann
5255
5356-- | Convert Pandoc to LaTeX.
@@ -241,152 +244,6 @@ pandocToLaTeX options (Pandoc meta blocks) = do
241244 Nothing -> main
242245 Just tpl -> renderTemplate tpl context'
243246
244−data StringContext = TextString
245− | URLString
246− | CodeString
247− deriving (Eq)
248−
249−-- escape things as needed for LaTeX
250−stringToLaTeX :: PandocMonad m => StringContext -> Text -> LW m Text
251−stringToLaTeX context zs = do
252− opts <- gets stOptions
253− return $ T.pack $
254− foldr (go opts context) mempty $ T.unpack $
255− if writerPreferAscii opts
256− then Normalize.normalize Normalize.NFD zs
257− else zs
258− where
259− go :: WriterOptions -> StringContext -> Char -> String -> String
260− go opts ctx x xs =
261− let ligatures = isEnabled Ext_smart opts && ctx == TextString
262− isUrl = ctx == URLString
263− mbAccentCmd =
264− if writerPreferAscii opts && ctx == TextString
265− then uncons xs >>= \(c,_) -> lookupAccent c
266− else Nothing
267− emits s =
268− case mbAccentCmd of
269− Just cmd ->
270− cmd <> "{" <> s <> "}" <> drop 1 xs -- drop combining accent
271− Nothing -> s <> xs
272− emitc c =
273− case mbAccentCmd of
274− Just cmd ->
275− cmd <> "{" <> [c] <> "}" <> drop 1 xs -- drop combining accent
276− Nothing -> c : xs
277− emitcseq cs =
278− case xs of
279− c:_ | isLetter c
280− , ctx == TextString
281− -> cs <> " " <> xs
282− | isSpace c -> cs <> "{}" <> xs
283− | ctx == TextString
284− -> cs <> xs
285− _ -> cs <> "{}" <> xs
286− emitquote cs =
287− case xs of
288− '`':_ -> cs <> "\\," <> xs -- add thin space
289− '\'':_ -> cs <> "\\," <> xs -- add thin space
290− _ -> cs <> xs
291− in case x of
292− '?' | ligatures -> -- avoid ?` ligature
293− case xs of
294− '`':_ -> emits "?{}"
295− _ -> emitc x
296− '!' | ligatures -> -- avoid !` ligature
297− case xs of
298− '`':_ -> emits "!{}"
299− _ -> emitc x
300− '{' -> emits "\\{"
301− '}' -> emits "\\}"
302− '`' | ctx == CodeString -> emitcseq "\\textasciigrave"
303− '$' | not isUrl -> emits "\\$"
304− '%' -> emits "\\%"
305− '&' -> emits "\\&"
306− '_' | not isUrl -> emits "\\_"
307− '#' -> emits "\\#"
308− '-' | not isUrl -> case xs of
309− -- prevent adjacent hyphens from forming ligatures
310− ('-':_) -> emits "-\\/"
311− _ -> emitc '-'
312− '~' | not isUrl -> emitcseq "\\textasciitilde"
313− '^' -> emits "\\^{}"
314− '\\'| isUrl -> emitc '/' -- NB. / works as path sep even on Windows
315− | otherwise -> emitcseq "\\textbackslash"
316− '|' | not isUrl -> emitcseq "\\textbar"
317− '<' -> emitcseq "\\textless"
318− '>' -> emitcseq "\\textgreater"
319− '[' -> emits "{[}" -- to avoid interpretation as
320− ']' -> emits "{]}" -- optional arguments
321− '\'' | ctx == CodeString -> emitcseq "\\textquotesingle"
322− '\160' -> emits "~"
323− '\x200B' -> emits "\\hspace{0pt}" -- zero-width space
324− '\x202F' -> emits "\\,"
325− '\x2026' -> emitcseq "\\ldots"
326− '\x2018' | ligatures -> emitquote "`"
327− '\x2019' | ligatures -> emitquote "'"
328− '\x201C' | ligatures -> emitquote "``"
329− '\x201D' | ligatures -> emitquote "''"
330− '\x2014' | ligatures -> emits "---"
331− '\x2013' | ligatures -> emits "--"
332− _ | writerPreferAscii opts
333− -> case x of
334− 'ı' -> emitcseq "\\i"
335− 'ȷ' -> emitcseq "\\j"
336− 'å' -> emitcseq "\\aa"
337− 'Å' -> emitcseq "\\AA"
338− 'ß' -> emitcseq "\\ss"
339− 'ø' -> emitcseq "\\o"
340− 'Ø' -> emitcseq "\\O"
341− 'Ł' -> emitcseq "\\L"
342− 'ł' -> emitcseq "\\l"
343− 'æ' -> emitcseq "\\ae"
344− 'Æ' -> emitcseq "\\AE"
345− 'œ' -> emitcseq "\\oe"
346− 'Œ' -> emitcseq "\\OE"
347− '£' -> emitcseq "\\pounds"
348− '€' -> emitcseq "\\euro"
349− '©' -> emitcseq "\\copyright"
350− _ -> emitc x
351− | otherwise -> emitc x
352−
353−lookupAccent :: Char -> Maybe String
354−lookupAccent '\779' = Just "\\H"
355−lookupAccent '\768' = Just "\\`"
356−lookupAccent '\769' = Just "\\'"
357−lookupAccent '\770' = Just "\\^"
358−lookupAccent '\771' = Just "\\~"
359−lookupAccent '\776' = Just "\\\""
360−lookupAccent '\775' = Just "\\."
361−lookupAccent '\772' = Just "\\="
362−lookupAccent '\781' = Just "\\|"
363−lookupAccent '\817' = Just "\\b"
364−lookupAccent '\807' = Just "\\c"
365−lookupAccent '\783' = Just "\\G"
366−lookupAccent '\777' = Just "\\h"
367−lookupAccent '\803' = Just "\\d"
368−lookupAccent '\785' = Just "\\f"
369−lookupAccent '\778' = Just "\\r"
370−lookupAccent '\865' = Just "\\t"
371−lookupAccent '\782' = Just "\\U"
372−lookupAccent '\780' = Just "\\v"
373−lookupAccent '\774' = Just "\\u"
374−lookupAccent '\808' = Just "\\k"
375−lookupAccent '\8413' = Just "\\textcircled"
376−lookupAccent _ = Nothing
377−
378−toLabel :: PandocMonad m => Text -> LW m Text
379−toLabel z = go `fmap` stringToLaTeX URLString z
380− where
381− go = T.concatMap $ \x -> case x of
382− _ | (isLetter x || isDigit x) && isAscii x -> T.singleton x
383− | x `elemText` "_-+=:;." -> T.singleton x
384− | otherwise -> T.pack $ "ux" <> printf "%x" (ord x)
385−
386−-- | Puts contents into LaTeX command.
387−inCmd :: Text -> Doc Text -> Doc Text
388−inCmd cmd contents = char '\\' <> literal cmd <> braces contents
389−
390247toSlides :: PandocMonad m => [Block] -> LW m [Block]
391248toSlides bs = do
392249 opts <- gets stOptions
@@ -851,81 +708,6 @@ sectionHeader classes ident level lst = do
851708 braces txtNoNotes
852709 else empty
853710
854−mapAlignment :: Text -> Text
855−mapAlignment a = case a of
856− "top" -> "T"
857− "top-baseline" -> "t"
858− "bottom" -> "b"
859− "center" -> "c"
860− _ -> a
861−
862−wrapDiv :: PandocMonad m => Attr -> Doc Text -> LW m (Doc Text)
863−wrapDiv (_,classes,kvs) t = do
864− beamer <- gets stBeamer
865− let align dir txt = inCmd "begin" dir $$ txt $$ inCmd "end" dir
866− lang <- toLang $ lookup "lang" kvs
867− let wrapColumns = if beamer && "columns" `elem` classes
868− then \contents ->
869− let valign = maybe "T" mapAlignment (lookup "align" kvs)
870− totalwidth = maybe [] (\x -> ["totalwidth=" <> x])
871− (lookup "totalwidth" kvs)
872− onlytextwidth = filter ("onlytextwidth" ==) classes
873− options = text $ T.unpack $ T.intercalate "," $
874− valign : totalwidth ++ onlytextwidth
875− in inCmd "begin" "columns" <> brackets options
876− $$ contents
877− $$ inCmd "end" "columns"
878− else id
879− wrapColumn = if beamer && "column" `elem` classes
880− then \contents ->
881− let valign =
882− maybe ""
883− (brackets . text . T.unpack . mapAlignment)
884− (lookup "align" kvs)
885− w = maybe "0.48" fromPct (lookup "width" kvs)
886− in inCmd "begin" "column" <>
887− valign <>
888− braces (literal w <> "\\textwidth")
889− $$ contents
890− $$ inCmd "end" "column"
891− else id
892− fromPct xs =
893− case T.unsnoc xs of
894− Just (ds, '%') -> case safeRead ds of
895− Just digits -> showFl (digits / 100 :: Double)
896− Nothing -> xs
897− _ -> xs
898− wrapDir = case lookup "dir" kvs of
899− Just "rtl" -> align "RTL"
900− Just "ltr" -> align "LTR"
901− _ -> id
902− wrapLang txt = case lang of
903− Just lng -> let (l, o) = toPolyglossiaEnv lng
904− ops = if T.null o
905− then ""
906− else brackets $ literal o
907− in inCmd "begin" (literal l) <> ops
908− $$ blankline <> txt <> blankline
909− $$ inCmd "end" (literal l)
910− Nothing -> txt
911− return $ wrapColumns . wrapColumn . wrapDir . wrapLang $ t
912−
913−hypertarget :: PandocMonad m => Bool -> Text -> Doc Text -> LW m (Doc Text)
914−hypertarget _ "" x = return x
915−hypertarget addnewline ident x = do
916− ref <- literal `fmap` toLabel ident
917− return $ text "\\hypertarget"
918− <> braces ref
919− <> braces ((if addnewline && not (isEmpty x)
920− then "%" <> cr
921− else empty) <> x)
922−
923−labelFor :: PandocMonad m => Text -> LW m (Doc Text)
924−labelFor "" = return empty
925−labelFor ident = do
926− ref <- literal `fmap` toLabel ident
927− return $ text "\\label" <> braces ref
928−
929711-- | Convert list of inline elements to LaTeX.
930712inlineListToLaTeX :: PandocMonad m
931713 => [Inline] -- ^ Inlines to convert
@@ -950,10 +732,6 @@ inlineListToLaTeX lst = hcat <$>
950732 fixInitialLineBreaks xs
951733 fixInitialLineBreaks xs = xs
952734
953−isQuoted :: Inline -> Bool
954−isQuoted (Quoted _ _) = True
955−isQuoted _ = False
956−
957735-- | Convert inline element to LaTeX
958736inlineToLaTeX :: PandocMonad m
959737 => Inline -- ^ Inline to convert
@@ -1014,8 +792,8 @@ inlineToLaTeX (Cite cits lst) = do
1014792 st <- get
1015793 let opts = stOptions st
1016794 case writerCiteMethod opts of
1017− Natbib -> citationsToNatbib cits
1018− Biblatex -> citationsToBiblatex cits
795+ Natbib -> citationsToNatbib inlineListToLaTeX cits
796+ Biblatex -> citationsToBiblatex inlineListToLaTeX cits
1019797 _ -> inlineListToLaTeX lst
1020798
1021799inlineToLaTeX (Code (_,classes,kvs) str) = do
@@ -1089,6 +867,9 @@ inlineToLaTeX (Quoted qt lst) = do
1089867 if isEnabled Ext_smart opts
1090868 then char '`' <> inner <> char '\''
1091869 else char '\x2018' <> inner <> char '\x2019'
870+ where
871+ isQuoted (Quoted _ _) = True
872+ isQuoted _ = False
1092873inlineToLaTeX (Str str) = do
1093874 setEmptyLine False
1094875 liftM literal $ stringToLaTeX TextString str
@@ -1226,153 +1007,6 @@ protectCode x = [x]
12261007setEmptyLine :: PandocMonad m => Bool -> LW m ()
12271008setEmptyLine b = modify $ \st -> st{ stEmptyLine = b }
12281009
1229−citationsToNatbib :: PandocMonad m => [Citation] -> LW m (Doc Text)
1230−citationsToNatbib
1231− [one]
1232− = citeCommand c p s k
1233− where
1234− Citation { citationId = k
1235− , citationPrefix = p
1236− , citationSuffix = s
1237− , citationMode = m
1238− }
1239− = one
1240− c = case m of
1241− AuthorInText -> "citet"
1242− SuppressAuthor -> "citeyearpar"
1243− NormalCitation -> "citep"
1244−
1245−citationsToNatbib cits
1246− | noPrefix (tail cits) && noSuffix (init cits) && ismode NormalCitation cits
1247− = citeCommand "citep" p s ks
1248− where
1249− noPrefix = all (null . citationPrefix)
1250− noSuffix = all (null . citationSuffix)
1251− ismode m = all ((==) m . citationMode)
1252− p = citationPrefix $
1253− head cits
1254− s = citationSuffix $
1255− last cits
1256− ks = T.intercalate ", " $ map citationId cits
1257−
1258−citationsToNatbib (c:cs) | citationMode c == AuthorInText = do
1259− author <- citeCommand "citeauthor" [] [] (citationId c)
1260− cits <- citationsToNatbib (c { citationMode = SuppressAuthor } : cs)
1261− return $ author <+> cits
1262−
1263−citationsToNatbib cits = do
1264− cits' <- mapM convertOne cits
1265− return $ text "\\citetext{" <> foldl' combineTwo empty cits' <> text "}"
1266− where
1267− combineTwo a b | isEmpty a = b
1268− | otherwise = a <> text "; " <> b
1269− convertOne Citation { citationId = k
1270− , citationPrefix = p
1271− , citationSuffix = s
1272− , citationMode = m
1273− }
1274− = case m of
1275− AuthorInText -> citeCommand "citealt" p s k
1276− SuppressAuthor -> citeCommand "citeyear" p s k
1277− NormalCitation -> citeCommand "citealp" p s k
1278−
1279−citeCommand :: PandocMonad m
1280− => Text -> [Inline] -> [Inline] -> Text -> LW m (Doc Text)
1281−citeCommand c p s k = do
1282− args <- citeArguments p s k
1283− return $ literal ("\\" <> c) <> args
1284−
1285−type Prefix = [Inline]
1286−type Suffix = [Inline]
1287−type CiteId = Text
1288−data CiteGroup = CiteGroup Prefix Suffix [CiteId]
1289−
1290−citeArgumentsList :: PandocMonad m
1291− => CiteGroup -> LW m (Doc Text)
1292−citeArgumentsList (CiteGroup _ _ []) = return empty
1293−citeArgumentsList (CiteGroup pfxs sfxs ids) = do
1294− pdoc <- inlineListToLaTeX pfxs
1295− sdoc <- inlineListToLaTeX sfxs'
1296− return $ optargs pdoc sdoc <>
1297− braces (literal (T.intercalate "," (reverse ids)))
1298− where sfxs' = stripLocatorBraces $ case sfxs of
1299− (Str t : r) -> case T.uncons t of
1300− Just (x, xs)
1301− | T.null xs
1302− , isPunctuation x -> dropWhile (== Space) r
1303− | isPunctuation x -> Str xs : r
1304− _ -> sfxs
1305− _ -> sfxs
1306− optargs pdoc sdoc = case (isEmpty pdoc, isEmpty sdoc) of
1307− (True, True ) -> empty
1308− (True, False) -> brackets sdoc
1309− (_ , _ ) -> brackets pdoc <> brackets sdoc
1310−
1311−citeArguments :: PandocMonad m
1312− => [Inline] -> [Inline] -> Text -> LW m (Doc Text)
1313−citeArguments p s k = citeArgumentsList (CiteGroup p s [k])
1314−
1315−-- strip off {} used to define locator in pandoc-citeproc; see #5722
1316−stripLocatorBraces :: [Inline] -> [Inline]
1317−stripLocatorBraces = walk go
1318− where go (Str xs) = Str $ T.filter (\c -> c /= '{' && c /= '}') xs
1319− go x = x
1320−
1321−citationsToBiblatex :: PandocMonad m => [Citation] -> LW m (Doc Text)
1322−citationsToBiblatex
1323− [one]
1324− = citeCommand cmd p s k
1325− where
1326− Citation { citationId = k
1327− , citationPrefix = p
1328− , citationSuffix = s
1329− , citationMode = m
1330− } = one
1331− cmd = case m of
1332− SuppressAuthor -> "autocite*"
1333− AuthorInText -> "textcite"
1334− NormalCitation -> "autocite"
1335−
1336−citationsToBiblatex (c:cs)
1337− | all (\cit -> null (citationPrefix cit) && null (citationSuffix cit)) (c:cs)
1338− = do
1339− let cmd = case citationMode c of
1340− SuppressAuthor -> "\\autocite*"
1341− AuthorInText -> "\\textcite"
1342− NormalCitation -> "\\autocite"
1343− return $ text cmd <>
1344− braces (literal (T.intercalate "," (map citationId (c:cs))))
1345− | otherwise
1346− = do
1347− let cmd = case citationMode c of
1348− SuppressAuthor -> "\\autocites*"
1349− AuthorInText -> "\\textcites"
1350− NormalCitation -> "\\autocites"
1351−
1352− groups <- mapM citeArgumentsList (reverse (foldl' grouper [] (c:cs)))
1353−
1354− return $ text cmd <> mconcat groups
1355−
1356− where grouper prev cit = case prev of
1357− ((CiteGroup oPfx oSfx ids):rest)
1358− | null oSfx && null pfx -> CiteGroup oPfx sfx (cid:ids) : rest
1359− _ -> CiteGroup pfx sfx [cid] : prev
1360− where pfx = citationPrefix cit
1361− sfx = citationSuffix cit
1362− cid = citationId cit
1363−
1364−citationsToBiblatex _ = return empty
1365−
1366−-- Determine listings language from list of class attributes.
1367−getListingsLanguage :: [Text] -> Maybe Text
1368−getListingsLanguage xs
1369− = foldr ((<|>) . toListingsLanguage) Nothing xs
1370−
1371−mbBraced :: Text -> Text
1372−mbBraced x = if not (T.all isAlphaNum x)
1373− then "{" <> x <> "}"
1374− else x
1375−
13761010-- Extract a key from divs and spans
13771011extract :: Text -> Block -> [Text]
13781012extract key (Div attr _) = lookKey key attr
@@ -1390,174 +1024,4 @@ extractInline _ _ = []
13901024lookKey :: Text -> Attr -> [Text]
13911025lookKey key (_,_,kvs) = maybe [] T.words $ lookup key kvs
13921026
1393−-- In environments \Arabic instead of \arabic is used
1394−toPolyglossiaEnv :: Lang -> (Text, Text)
1395−toPolyglossiaEnv l =
1396− case toPolyglossia l of
1397− ("arabic", o) -> ("Arabic", o)
1398− x -> x
1399−
1400−-- Takes a list of the constituents of a BCP 47 language code and
1401−-- converts it to a Polyglossia (language, options) tuple
1402−-- http://mirrors.ctan.org/macros/latex/contrib/polyglossia/polyglossia.pdf
1403−toPolyglossia :: Lang -> (Text, Text)
1404−toPolyglossia (Lang "ar" _ "DZ" _) = ("arabic", "locale=algeria")
1405−toPolyglossia (Lang "ar" _ "IQ" _) = ("arabic", "locale=mashriq")
1406−toPolyglossia (Lang "ar" _ "JO" _) = ("arabic", "locale=mashriq")
1407−toPolyglossia (Lang "ar" _ "LB" _) = ("arabic", "locale=mashriq")
1408−toPolyglossia (Lang "ar" _ "LY" _) = ("arabic", "locale=libya")
1409−toPolyglossia (Lang "ar" _ "MA" _) = ("arabic", "locale=morocco")
1410−toPolyglossia (Lang "ar" _ "MR" _) = ("arabic", "locale=mauritania")
1411−toPolyglossia (Lang "ar" _ "PS" _) = ("arabic", "locale=mashriq")
1412−toPolyglossia (Lang "ar" _ "SY" _) = ("arabic", "locale=mashriq")
1413−toPolyglossia (Lang "ar" _ "TN" _) = ("arabic", "locale=tunisia")
1414−toPolyglossia (Lang "de" _ _ vars)
1415− | "1901" `elem` vars = ("german", "spelling=old")
1416−toPolyglossia (Lang "de" _ "AT" vars)
1417− | "1901" `elem` vars = ("german", "variant=austrian, spelling=old")
1418−toPolyglossia (Lang "de" _ "AT" _) = ("german", "variant=austrian")
1419−toPolyglossia (Lang "de" _ "CH" vars)
1420− | "1901" `elem` vars = ("german", "variant=swiss, spelling=old")
1421−toPolyglossia (Lang "de" _ "CH" _) = ("german", "variant=swiss")
1422−toPolyglossia (Lang "de" _ _ _) = ("german", "")
1423−toPolyglossia (Lang "dsb" _ _ _) = ("lsorbian", "")
1424−toPolyglossia (Lang "el" _ "polyton" _) = ("greek", "variant=poly")
1425−toPolyglossia (Lang "en" _ "AU" _) = ("english", "variant=australian")
1426−toPolyglossia (Lang "en" _ "CA" _) = ("english", "variant=canadian")
1427−toPolyglossia (Lang "en" _ "GB" _) = ("english", "variant=british")
1428−toPolyglossia (Lang "en" _ "NZ" _) = ("english", "variant=newzealand")
1429−toPolyglossia (Lang "en" _ "UK" _) = ("english", "variant=british")
1430−toPolyglossia (Lang "en" _ "US" _) = ("english", "variant=american")
1431−toPolyglossia (Lang "grc" _ _ _) = ("greek", "variant=ancient")
1432−toPolyglossia (Lang "hsb" _ _ _) = ("usorbian", "")
1433−toPolyglossia (Lang "la" _ _ vars)
1434− | "x-classic" `elem` vars = ("latin", "variant=classic")
1435−toPolyglossia (Lang "pt" _ "BR" _) = ("portuguese", "variant=brazilian")
1436−toPolyglossia (Lang "sl" _ _ _) = ("slovenian", "")
1437−toPolyglossia x = (commonFromBcp47 x, "")
14381027
1439−-- Takes a list of the constituents of a BCP 47 language code and
1440−-- converts it to a Babel language string.
1441−-- http://mirrors.ctan.org/macros/latex/required/babel/base/babel.pdf
1442−-- List of supported languages (slightly outdated):
1443−-- http://tug.ctan.org/language/hyph-utf8/doc/generic/hyph-utf8/hyphenation.pdf
1444−toBabel :: Lang -> Text
1445−toBabel (Lang "de" _ "AT" vars)
1446− | "1901" `elem` vars = "austrian"
1447− | otherwise = "naustrian"
1448−toBabel (Lang "de" _ "CH" vars)
1449− | "1901" `elem` vars = "swissgerman"
1450− | otherwise = "nswissgerman"
1451−toBabel (Lang "de" _ _ vars)
1452− | "1901" `elem` vars = "german"
1453− | otherwise = "ngerman"
1454−toBabel (Lang "dsb" _ _ _) = "lowersorbian"
1455−toBabel (Lang "el" _ _ vars)
1456− | "polyton" `elem` vars = "polutonikogreek"
1457−toBabel (Lang "en" _ "AU" _) = "australian"
1458−toBabel (Lang "en" _ "CA" _) = "canadian"
1459−toBabel (Lang "en" _ "GB" _) = "british"
1460−toBabel (Lang "en" _ "NZ" _) = "newzealand"
1461−toBabel (Lang "en" _ "UK" _) = "british"
1462−toBabel (Lang "en" _ "US" _) = "american"
1463−toBabel (Lang "fr" _ "CA" _) = "canadien"
1464−toBabel (Lang "fra" _ _ vars)
1465− | "aca" `elem` vars = "acadian"
1466−toBabel (Lang "grc" _ _ _) = "polutonikogreek"
1467−toBabel (Lang "hsb" _ _ _) = "uppersorbian"
1468−toBabel (Lang "la" _ _ vars)
1469− | "x-classic" `elem` vars = "classiclatin"
1470−toBabel (Lang "pt" _ "BR" _) = "brazilian"
1471−toBabel (Lang "sl" _ _ _) = "slovene"
1472−toBabel x = commonFromBcp47 x
1473−
1474−-- Takes a list of the constituents of a BCP 47 language code
1475−-- and converts it to a string shared by Babel and Polyglossia.
1476−-- https://tools.ietf.org/html/bcp47#section-2.1
1477−commonFromBcp47 :: Lang -> Text
1478−commonFromBcp47 (Lang "sr" "Cyrl" _ _) = "serbianc"
1479−commonFromBcp47 (Lang "zh" "Latn" _ vars)
1480− | "pinyin" `elem` vars = "pinyin"
1481−commonFromBcp47 (Lang l _ _ _) = fromIso l
1482− where
1483− fromIso "af" = "afrikaans"
1484− fromIso "am" = "amharic"
1485− fromIso "ar" = "arabic"
1486− fromIso "as" = "assamese"
1487− fromIso "ast" = "asturian"
1488− fromIso "bg" = "bulgarian"
1489− fromIso "bn" = "bengali"
1490− fromIso "bo" = "tibetan"
1491− fromIso "br" = "breton"
1492− fromIso "ca" = "catalan"
1493− fromIso "cy" = "welsh"
1494− fromIso "cs" = "czech"
1495− fromIso "cop" = "coptic"
1496− fromIso "da" = "danish"
1497− fromIso "dv" = "divehi"
1498− fromIso "el" = "greek"
1499− fromIso "en" = "english"
1500− fromIso "eo" = "esperanto"
1501− fromIso "es" = "spanish"
1502− fromIso "et" = "estonian"
1503− fromIso "eu" = "basque"
1504− fromIso "fa" = "farsi"
1505− fromIso "fi" = "finnish"
1506− fromIso "fr" = "french"
1507− fromIso "fur" = "friulan"
1508− fromIso "ga" = "irish"
1509− fromIso "gd" = "scottish"
1510− fromIso "gez" = "ethiopic"
1511− fromIso "gl" = "galician"
1512− fromIso "he" = "hebrew"
1513− fromIso "hi" = "hindi"
1514− fromIso "hr" = "croatian"
1515− fromIso "hu" = "magyar"
1516− fromIso "hy" = "armenian"
1517− fromIso "ia" = "interlingua"
1518− fromIso "id" = "indonesian"
1519− fromIso "ie" = "interlingua"
1520− fromIso "is" = "icelandic"
1521− fromIso "it" = "italian"
1522− fromIso "ja" = "japanese"
1523− fromIso "km" = "khmer"
1524− fromIso "kmr" = "kurmanji"
1525− fromIso "kn" = "kannada"
1526− fromIso "ko" = "korean"
1527− fromIso "la" = "latin"
1528− fromIso "lo" = "lao"
1529− fromIso "lt" = "lithuanian"
1530− fromIso "lv" = "latvian"
1531− fromIso "ml" = "malayalam"
1532− fromIso "mn" = "mongolian"
1533− fromIso "mr" = "marathi"
1534− fromIso "nb" = "norsk"
1535− fromIso "nl" = "dutch"
1536− fromIso "nn" = "nynorsk"
1537− fromIso "no" = "norsk"
1538− fromIso "nqo" = "nko"
1539− fromIso "oc" = "occitan"
1540− fromIso "pa" = "panjabi"
1541− fromIso "pl" = "polish"
1542− fromIso "pms" = "piedmontese"
1543− fromIso "pt" = "portuguese"
1544− fromIso "rm" = "romansh"
1545− fromIso "ro" = "romanian"
1546− fromIso "ru" = "russian"
1547− fromIso "sa" = "sanskrit"
1548− fromIso "se" = "samin"
1549− fromIso "sk" = "slovak"
1550− fromIso "sq" = "albanian"
1551− fromIso "sr" = "serbian"
1552− fromIso "sv" = "swedish"
1553− fromIso "syr" = "syriac"
1554− fromIso "ta" = "tamil"
1555− fromIso "te" = "telugu"
1556− fromIso "th" = "thai"
1557− fromIso "ti" = "ethiopic"
1558− fromIso "tk" = "turkmen"
1559− fromIso "tr" = "turkish"
1560− fromIso "uk" = "ukrainian"
1561− fromIso "ur" = "urdu"
1562− fromIso "vi" = "vietnamese"
1563− fromIso _ = ""
src/Text/Pandoc/Writers/LaTeX/Citation.hsnew file+181
@@ -0,0 +1,181 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{- |
3+ Module : Text.Pandoc.Writers.LaTeX.Citation
4+ Copyright : Copyright (C) 2006-2021 John MacFarlane
5+ License : GNU GPL, version 2 or above
6+
7+ Maintainer : John MacFarlane <jgm@berkeley.edu>
8+ Stability : alpha
9+ Portability : portable
10+-}
11+module Text.Pandoc.Writers.LaTeX.Citation
12+ ( citationsToNatbib,
13+ citationsToBiblatex
14+ ) where
15+
16+import Data.Text (Text)
17+import Data.Char (isPunctuation)
18+import qualified Data.Text as T
19+import Text.Pandoc.Class.PandocMonad (PandocMonad)
20+import Text.Pandoc.Definition
21+import Data.List (foldl')
22+import Text.DocLayout (Doc, brackets, empty, (<+>), text, isEmpty, literal,
23+ braces)
24+import Text.Pandoc.Walk
25+import Text.Pandoc.Writers.LaTeX.Types ( LW )
26+
27+citationsToNatbib :: PandocMonad m
28+ => ([Inline] -> LW m (Doc Text))
29+ -> [Citation]
30+ -> LW m (Doc Text)
31+citationsToNatbib inlineListToLaTeX [one]
32+ = citeCommand inlineListToLaTeX c p s k
33+ where
34+ Citation { citationId = k
35+ , citationPrefix = p
36+ , citationSuffix = s
37+ , citationMode = m
38+ }
39+ = one
40+ c = case m of
41+ AuthorInText -> "citet"
42+ SuppressAuthor -> "citeyearpar"
43+ NormalCitation -> "citep"
44+
45+citationsToNatbib inlineListToLaTeX cits
46+ | noPrefix (tail cits) && noSuffix (init cits) && ismode NormalCitation cits
47+ = citeCommand inlineListToLaTeX "citep" p s ks
48+ where
49+ noPrefix = all (null . citationPrefix)
50+ noSuffix = all (null . citationSuffix)
51+ ismode m = all ((==) m . citationMode)
52+ p = citationPrefix $
53+ head cits
54+ s = citationSuffix $
55+ last cits
56+ ks = T.intercalate ", " $ map citationId cits
57+
58+citationsToNatbib inlineListToLaTeX (c:cs)
59+ | citationMode c == AuthorInText = do
60+ author <- citeCommand inlineListToLaTeX "citeauthor" [] [] (citationId c)
61+ cits <- citationsToNatbib inlineListToLaTeX
62+ (c { citationMode = SuppressAuthor } : cs)
63+ return $ author <+> cits
64+
65+citationsToNatbib inlineListToLaTeX cits = do
66+ cits' <- mapM convertOne cits
67+ return $ text "\\citetext{" <> foldl' combineTwo empty cits' <> text "}"
68+ where
69+ citeCommand' = citeCommand inlineListToLaTeX
70+ combineTwo a b | isEmpty a = b
71+ | otherwise = a <> text "; " <> b
72+ convertOne Citation { citationId = k
73+ , citationPrefix = p
74+ , citationSuffix = s
75+ , citationMode = m
76+ }
77+ = case m of
78+ AuthorInText -> citeCommand' "citealt" p s k
79+ SuppressAuthor -> citeCommand' "citeyear" p s k
80+ NormalCitation -> citeCommand' "citealp" p s k
81+
82+citeCommand :: PandocMonad m
83+ => ([Inline] -> LW m (Doc Text))
84+ -> Text
85+ -> [Inline]
86+ -> [Inline]
87+ -> Text
88+ -> LW m (Doc Text)
89+citeCommand inlineListToLaTeX c p s k = do
90+ args <- citeArguments inlineListToLaTeX p s k
91+ return $ literal ("\\" <> c) <> args
92+
93+type Prefix = [Inline]
94+type Suffix = [Inline]
95+type CiteId = Text
96+data CiteGroup = CiteGroup Prefix Suffix [CiteId]
97+
98+citeArgumentsList :: PandocMonad m
99+ => ([Inline] -> LW m (Doc Text))
100+ -> CiteGroup
101+ -> LW m (Doc Text)
102+citeArgumentsList _inlineListToLaTeX (CiteGroup _ _ []) = return empty
103+citeArgumentsList inlineListToLaTeX (CiteGroup pfxs sfxs ids) = do
104+ pdoc <- inlineListToLaTeX pfxs
105+ sdoc <- inlineListToLaTeX sfxs'
106+ return $ optargs pdoc sdoc <>
107+ braces (literal (T.intercalate "," (reverse ids)))
108+ where sfxs' = stripLocatorBraces $ case sfxs of
109+ (Str t : r) -> case T.uncons t of
110+ Just (x, xs)
111+ | T.null xs
112+ , isPunctuation x -> dropWhile (== Space) r
113+ | isPunctuation x -> Str xs : r
114+ _ -> sfxs
115+ _ -> sfxs
116+ optargs pdoc sdoc = case (isEmpty pdoc, isEmpty sdoc) of
117+ (True, True ) -> empty
118+ (True, False) -> brackets sdoc
119+ (_ , _ ) -> brackets pdoc <> brackets sdoc
120+
121+citeArguments :: PandocMonad m
122+ => ([Inline] -> LW m (Doc Text))
123+ -> [Inline]
124+ -> [Inline]
125+ -> Text
126+ -> LW m (Doc Text)
127+citeArguments inlineListToLaTeX p s k =
128+ citeArgumentsList inlineListToLaTeX (CiteGroup p s [k])
129+
130+-- strip off {} used to define locator in pandoc-citeproc; see #5722
131+stripLocatorBraces :: [Inline] -> [Inline]
132+stripLocatorBraces = walk go
133+ where go (Str xs) = Str $ T.filter (\c -> c /= '{' && c /= '}') xs
134+ go x = x
135+
136+citationsToBiblatex :: PandocMonad m
137+ => ([Inline] -> LW m (Doc Text))
138+ -> [Citation] -> LW m (Doc Text)
139+citationsToBiblatex inlineListToLaTeX [one]
140+ = citeCommand inlineListToLaTeX cmd p s k
141+ where
142+ Citation { citationId = k
143+ , citationPrefix = p
144+ , citationSuffix = s
145+ , citationMode = m
146+ } = one
147+ cmd = case m of
148+ SuppressAuthor -> "autocite*"
149+ AuthorInText -> "textcite"
150+ NormalCitation -> "autocite"
151+
152+citationsToBiblatex inlineListToLaTeX (c:cs)
153+ | all (\cit -> null (citationPrefix cit) && null (citationSuffix cit)) (c:cs)
154+ = do
155+ let cmd = case citationMode c of
156+ SuppressAuthor -> "\\autocite*"
157+ AuthorInText -> "\\textcite"
158+ NormalCitation -> "\\autocite"
159+ return $ text cmd <>
160+ braces (literal (T.intercalate "," (map citationId (c:cs))))
161+ | otherwise
162+ = do
163+ let cmd = case citationMode c of
164+ SuppressAuthor -> "\\autocites*"
165+ AuthorInText -> "\\textcites"
166+ NormalCitation -> "\\autocites"
167+
168+ groups <- mapM (citeArgumentsList inlineListToLaTeX)
169+ (reverse (foldl' grouper [] (c:cs)))
170+
171+ return $ text cmd <> mconcat groups
172+
173+ where grouper prev cit = case prev of
174+ ((CiteGroup oPfx oSfx ids):rest)
175+ | null oSfx && null pfx -> CiteGroup oPfx sfx (cid:ids) : rest
176+ _ -> CiteGroup pfx sfx [cid] : prev
177+ where pfx = citationPrefix cit
178+ sfx = citationSuffix cit
179+ cid = citationId cit
180+
181+citationsToBiblatex _ _ = return empty
src/Text/Pandoc/Writers/LaTeX/Lang.hsnew file+191
@@ -0,0 +1,191 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+{- |
4+ Module : Text.Pandoc.Writers.LaTeX.Lang
5+ Copyright : Copyright (C) 2006-2021 John MacFarlane
6+ License : GNU GPL, version 2 or above
7+
8+ Maintainer : John MacFarlane <jgm@berkeley.edu>
9+ Stability : alpha
10+ Portability : portable
11+-}
12+module Text.Pandoc.Writers.LaTeX.Lang
13+ ( toPolyglossiaEnv,
14+ toPolyglossia,
15+ toBabel
16+ ) where
17+import Data.Text (Text)
18+import Text.Pandoc.BCP47 (Lang (..))
19+
20+
21+-- In environments \Arabic instead of \arabic is used
22+toPolyglossiaEnv :: Lang -> (Text, Text)
23+toPolyglossiaEnv l =
24+ case toPolyglossia l of
25+ ("arabic", o) -> ("Arabic", o)
26+ x -> x
27+
28+-- Takes a list of the constituents of a BCP 47 language code and
29+-- converts it to a Polyglossia (language, options) tuple
30+-- http://mirrors.ctan.org/macros/latex/contrib/polyglossia/polyglossia.pdf
31+toPolyglossia :: Lang -> (Text, Text)
32+toPolyglossia (Lang "ar" _ "DZ" _) = ("arabic", "locale=algeria")
33+toPolyglossia (Lang "ar" _ "IQ" _) = ("arabic", "locale=mashriq")
34+toPolyglossia (Lang "ar" _ "JO" _) = ("arabic", "locale=mashriq")
35+toPolyglossia (Lang "ar" _ "LB" _) = ("arabic", "locale=mashriq")
36+toPolyglossia (Lang "ar" _ "LY" _) = ("arabic", "locale=libya")
37+toPolyglossia (Lang "ar" _ "MA" _) = ("arabic", "locale=morocco")
38+toPolyglossia (Lang "ar" _ "MR" _) = ("arabic", "locale=mauritania")
39+toPolyglossia (Lang "ar" _ "PS" _) = ("arabic", "locale=mashriq")
40+toPolyglossia (Lang "ar" _ "SY" _) = ("arabic", "locale=mashriq")
41+toPolyglossia (Lang "ar" _ "TN" _) = ("arabic", "locale=tunisia")
42+toPolyglossia (Lang "de" _ _ vars)
43+ | "1901" `elem` vars = ("german", "spelling=old")
44+toPolyglossia (Lang "de" _ "AT" vars)
45+ | "1901" `elem` vars = ("german", "variant=austrian, spelling=old")
46+toPolyglossia (Lang "de" _ "AT" _) = ("german", "variant=austrian")
47+toPolyglossia (Lang "de" _ "CH" vars)
48+ | "1901" `elem` vars = ("german", "variant=swiss, spelling=old")
49+toPolyglossia (Lang "de" _ "CH" _) = ("german", "variant=swiss")
50+toPolyglossia (Lang "de" _ _ _) = ("german", "")
51+toPolyglossia (Lang "dsb" _ _ _) = ("lsorbian", "")
52+toPolyglossia (Lang "el" _ "polyton" _) = ("greek", "variant=poly")
53+toPolyglossia (Lang "en" _ "AU" _) = ("english", "variant=australian")
54+toPolyglossia (Lang "en" _ "CA" _) = ("english", "variant=canadian")
55+toPolyglossia (Lang "en" _ "GB" _) = ("english", "variant=british")
56+toPolyglossia (Lang "en" _ "NZ" _) = ("english", "variant=newzealand")
57+toPolyglossia (Lang "en" _ "UK" _) = ("english", "variant=british")
58+toPolyglossia (Lang "en" _ "US" _) = ("english", "variant=american")
59+toPolyglossia (Lang "grc" _ _ _) = ("greek", "variant=ancient")
60+toPolyglossia (Lang "hsb" _ _ _) = ("usorbian", "")
61+toPolyglossia (Lang "la" _ _ vars)
62+ | "x-classic" `elem` vars = ("latin", "variant=classic")
63+toPolyglossia (Lang "pt" _ "BR" _) = ("portuguese", "variant=brazilian")
64+toPolyglossia (Lang "sl" _ _ _) = ("slovenian", "")
65+toPolyglossia x = (commonFromBcp47 x, "")
66+
67+-- Takes a list of the constituents of a BCP 47 language code and
68+-- converts it to a Babel language string.
69+-- http://mirrors.ctan.org/macros/latex/required/babel/base/babel.pdf
70+-- List of supported languages (slightly outdated):
71+-- http://tug.ctan.org/language/hyph-utf8/doc/generic/hyph-utf8/hyphenation.pdf
72+toBabel :: Lang -> Text
73+toBabel (Lang "de" _ "AT" vars)
74+ | "1901" `elem` vars = "austrian"
75+ | otherwise = "naustrian"
76+toBabel (Lang "de" _ "CH" vars)
77+ | "1901" `elem` vars = "swissgerman"
78+ | otherwise = "nswissgerman"
79+toBabel (Lang "de" _ _ vars)
80+ | "1901" `elem` vars = "german"
81+ | otherwise = "ngerman"
82+toBabel (Lang "dsb" _ _ _) = "lowersorbian"
83+toBabel (Lang "el" _ _ vars)
84+ | "polyton" `elem` vars = "polutonikogreek"
85+toBabel (Lang "en" _ "AU" _) = "australian"
86+toBabel (Lang "en" _ "CA" _) = "canadian"
87+toBabel (Lang "en" _ "GB" _) = "british"
88+toBabel (Lang "en" _ "NZ" _) = "newzealand"
89+toBabel (Lang "en" _ "UK" _) = "british"
90+toBabel (Lang "en" _ "US" _) = "american"
91+toBabel (Lang "fr" _ "CA" _) = "canadien"
92+toBabel (Lang "fra" _ _ vars)
93+ | "aca" `elem` vars = "acadian"
94+toBabel (Lang "grc" _ _ _) = "polutonikogreek"
95+toBabel (Lang "hsb" _ _ _) = "uppersorbian"
96+toBabel (Lang "la" _ _ vars)
97+ | "x-classic" `elem` vars = "classiclatin"
98+toBabel (Lang "pt" _ "BR" _) = "brazilian"
99+toBabel (Lang "sl" _ _ _) = "slovene"
100+toBabel x = commonFromBcp47 x
101+
102+-- Takes a list of the constituents of a BCP 47 language code
103+-- and converts it to a string shared by Babel and Polyglossia.
104+-- https://tools.ietf.org/html/bcp47#section-2.1
105+commonFromBcp47 :: Lang -> Text
106+commonFromBcp47 (Lang "sr" "Cyrl" _ _) = "serbianc"
107+commonFromBcp47 (Lang "zh" "Latn" _ vars)
108+ | "pinyin" `elem` vars = "pinyin"
109+commonFromBcp47 (Lang l _ _ _) = fromIso l
110+ where
111+ fromIso "af" = "afrikaans"
112+ fromIso "am" = "amharic"
113+ fromIso "ar" = "arabic"
114+ fromIso "as" = "assamese"
115+ fromIso "ast" = "asturian"
116+ fromIso "bg" = "bulgarian"
117+ fromIso "bn" = "bengali"
118+ fromIso "bo" = "tibetan"
119+ fromIso "br" = "breton"
120+ fromIso "ca" = "catalan"
121+ fromIso "cy" = "welsh"
122+ fromIso "cs" = "czech"
123+ fromIso "cop" = "coptic"
124+ fromIso "da" = "danish"
125+ fromIso "dv" = "divehi"
126+ fromIso "el" = "greek"
127+ fromIso "en" = "english"
128+ fromIso "eo" = "esperanto"
129+ fromIso "es" = "spanish"
130+ fromIso "et" = "estonian"
131+ fromIso "eu" = "basque"
132+ fromIso "fa" = "farsi"
133+ fromIso "fi" = "finnish"
134+ fromIso "fr" = "french"
135+ fromIso "fur" = "friulan"
136+ fromIso "ga" = "irish"
137+ fromIso "gd" = "scottish"
138+ fromIso "gez" = "ethiopic"
139+ fromIso "gl" = "galician"
140+ fromIso "he" = "hebrew"
141+ fromIso "hi" = "hindi"
142+ fromIso "hr" = "croatian"
143+ fromIso "hu" = "magyar"
144+ fromIso "hy" = "armenian"
145+ fromIso "ia" = "interlingua"
146+ fromIso "id" = "indonesian"
147+ fromIso "ie" = "interlingua"
148+ fromIso "is" = "icelandic"
149+ fromIso "it" = "italian"
150+ fromIso "ja" = "japanese"
151+ fromIso "km" = "khmer"
152+ fromIso "kmr" = "kurmanji"
153+ fromIso "kn" = "kannada"
154+ fromIso "ko" = "korean"
155+ fromIso "la" = "latin"
156+ fromIso "lo" = "lao"
157+ fromIso "lt" = "lithuanian"
158+ fromIso "lv" = "latvian"
159+ fromIso "ml" = "malayalam"
160+ fromIso "mn" = "mongolian"
161+ fromIso "mr" = "marathi"
162+ fromIso "nb" = "norsk"
163+ fromIso "nl" = "dutch"
164+ fromIso "nn" = "nynorsk"
165+ fromIso "no" = "norsk"
166+ fromIso "nqo" = "nko"
167+ fromIso "oc" = "occitan"
168+ fromIso "pa" = "panjabi"
169+ fromIso "pl" = "polish"
170+ fromIso "pms" = "piedmontese"
171+ fromIso "pt" = "portuguese"
172+ fromIso "rm" = "romansh"
173+ fromIso "ro" = "romanian"
174+ fromIso "ru" = "russian"
175+ fromIso "sa" = "sanskrit"
176+ fromIso "se" = "samin"
177+ fromIso "sk" = "slovak"
178+ fromIso "sq" = "albanian"
179+ fromIso "sr" = "serbian"
180+ fromIso "sv" = "swedish"
181+ fromIso "syr" = "syriac"
182+ fromIso "ta" = "tamil"
183+ fromIso "te" = "telugu"
184+ fromIso "th" = "thai"
185+ fromIso "ti" = "ethiopic"
186+ fromIso "tk" = "turkmen"
187+ fromIso "tr" = "turkish"
188+ fromIso "uk" = "ukrainian"
189+ fromIso "ur" = "urdu"
190+ fromIso "vi" = "vietnamese"
191+ fromIso _ = ""
src/Text/Pandoc/Writers/LaTeX/Util.hsnew file+274
@@ -0,0 +1,274 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{- |
3+ Module : Text.Pandoc.Writers.LaTeX.Util
4+ Copyright : Copyright (C) 2006-2021 John MacFarlane
5+ License : GNU GPL, version 2 or above
6+
7+ Maintainer : John MacFarlane <jgm@berkeley.edu>
8+ Stability : alpha
9+ Portability : portable
10+-}
11+module Text.Pandoc.Writers.LaTeX.Util (
12+ stringToLaTeX
13+ , StringContext(..)
14+ , toLabel
15+ , inCmd
16+ , wrapDiv
17+ , hypertarget
18+ , labelFor
19+ , getListingsLanguage
20+ , mbBraced
21+ )
22+where
23+
24+import Control.Applicative ((<|>))
25+import Text.Pandoc.Class (PandocMonad, toLang)
26+import Text.Pandoc.Options (WriterOptions(..), isEnabled)
27+import Text.Pandoc.Writers.LaTeX.Types (LW, WriterState(..))
28+import Text.Pandoc.Writers.LaTeX.Lang (toPolyglossiaEnv)
29+import Text.Pandoc.Highlighting (toListingsLanguage)
30+import Text.DocLayout
31+import Text.Pandoc.Definition
32+import Text.Pandoc.ImageSize (showFl)
33+import Control.Monad.State.Strict (gets)
34+import Data.Text (Text)
35+import qualified Data.Text as T
36+import Text.Pandoc.Extensions (Extension(Ext_smart))
37+import Data.Char (isLetter, isSpace, isDigit, isAscii, ord, isAlphaNum)
38+import Text.Printf (printf)
39+import Text.Pandoc.Shared (safeRead, elemText)
40+import qualified Data.Text.Normalize as Normalize
41+import Data.List (uncons)
42+
43+data StringContext = TextString
44+ | URLString
45+ | CodeString
46+ deriving (Eq)
47+
48+-- escape things as needed for LaTeX
49+stringToLaTeX :: PandocMonad m => StringContext -> Text -> LW m Text
50+stringToLaTeX context zs = do
51+ opts <- gets stOptions
52+ return $ T.pack $
53+ foldr (go opts context) mempty $ T.unpack $
54+ if writerPreferAscii opts
55+ then Normalize.normalize Normalize.NFD zs
56+ else zs
57+ where
58+ go :: WriterOptions -> StringContext -> Char -> String -> String
59+ go opts ctx x xs =
60+ let ligatures = isEnabled Ext_smart opts && ctx == TextString
61+ isUrl = ctx == URLString
62+ mbAccentCmd =
63+ if writerPreferAscii opts && ctx == TextString
64+ then uncons xs >>= \(c,_) -> lookupAccent c
65+ else Nothing
66+ emits s =
67+ case mbAccentCmd of
68+ Just cmd ->
69+ cmd <> "{" <> s <> "}" <> drop 1 xs -- drop combining accent
70+ Nothing -> s <> xs
71+ emitc c =
72+ case mbAccentCmd of
73+ Just cmd ->
74+ cmd <> "{" <> [c] <> "}" <> drop 1 xs -- drop combining accent
75+ Nothing -> c : xs
76+ emitcseq cs =
77+ case xs of
78+ c:_ | isLetter c
79+ , ctx == TextString
80+ -> cs <> " " <> xs
81+ | isSpace c -> cs <> "{}" <> xs
82+ | ctx == TextString
83+ -> cs <> xs
84+ _ -> cs <> "{}" <> xs
85+ emitquote cs =
86+ case xs of
87+ '`':_ -> cs <> "\\," <> xs -- add thin space
88+ '\'':_ -> cs <> "\\," <> xs -- add thin space
89+ _ -> cs <> xs
90+ in case x of
91+ '?' | ligatures -> -- avoid ?` ligature
92+ case xs of
93+ '`':_ -> emits "?{}"
94+ _ -> emitc x
95+ '!' | ligatures -> -- avoid !` ligature
96+ case xs of
97+ '`':_ -> emits "!{}"
98+ _ -> emitc x
99+ '{' -> emits "\\{"
100+ '}' -> emits "\\}"
101+ '`' | ctx == CodeString -> emitcseq "\\textasciigrave"
102+ '$' | not isUrl -> emits "\\$"
103+ '%' -> emits "\\%"
104+ '&' -> emits "\\&"
105+ '_' | not isUrl -> emits "\\_"
106+ '#' -> emits "\\#"
107+ '-' | not isUrl -> case xs of
108+ -- prevent adjacent hyphens from forming ligatures
109+ ('-':_) -> emits "-\\/"
110+ _ -> emitc '-'
111+ '~' | not isUrl -> emitcseq "\\textasciitilde"
112+ '^' -> emits "\\^{}"
113+ '\\'| isUrl -> emitc '/' -- NB. / works as path sep even on Windows
114+ | otherwise -> emitcseq "\\textbackslash"
115+ '|' | not isUrl -> emitcseq "\\textbar"
116+ '<' -> emitcseq "\\textless"
117+ '>' -> emitcseq "\\textgreater"
118+ '[' -> emits "{[}" -- to avoid interpretation as
119+ ']' -> emits "{]}" -- optional arguments
120+ '\'' | ctx == CodeString -> emitcseq "\\textquotesingle"
121+ '\160' -> emits "~"
122+ '\x200B' -> emits "\\hspace{0pt}" -- zero-width space
123+ '\x202F' -> emits "\\,"
124+ '\x2026' -> emitcseq "\\ldots"
125+ '\x2018' | ligatures -> emitquote "`"
126+ '\x2019' | ligatures -> emitquote "'"
127+ '\x201C' | ligatures -> emitquote "``"
128+ '\x201D' | ligatures -> emitquote "''"
129+ '\x2014' | ligatures -> emits "---"
130+ '\x2013' | ligatures -> emits "--"
131+ _ | writerPreferAscii opts
132+ -> case x of
133+ 'ı' -> emitcseq "\\i"
134+ 'ȷ' -> emitcseq "\\j"
135+ 'å' -> emitcseq "\\aa"
136+ 'Å' -> emitcseq "\\AA"
137+ 'ß' -> emitcseq "\\ss"
138+ 'ø' -> emitcseq "\\o"
139+ 'Ø' -> emitcseq "\\O"
140+ 'Ł' -> emitcseq "\\L"
141+ 'ł' -> emitcseq "\\l"
142+ 'æ' -> emitcseq "\\ae"
143+ 'Æ' -> emitcseq "\\AE"
144+ 'œ' -> emitcseq "\\oe"
145+ 'Œ' -> emitcseq "\\OE"
146+ '£' -> emitcseq "\\pounds"
147+ '€' -> emitcseq "\\euro"
148+ '©' -> emitcseq "\\copyright"
149+ _ -> emitc x
150+ | otherwise -> emitc x
151+
152+lookupAccent :: Char -> Maybe String
153+lookupAccent '\779' = Just "\\H"
154+lookupAccent '\768' = Just "\\`"
155+lookupAccent '\769' = Just "\\'"
156+lookupAccent '\770' = Just "\\^"
157+lookupAccent '\771' = Just "\\~"
158+lookupAccent '\776' = Just "\\\""
159+lookupAccent '\775' = Just "\\."
160+lookupAccent '\772' = Just "\\="
161+lookupAccent '\781' = Just "\\|"
162+lookupAccent '\817' = Just "\\b"
163+lookupAccent '\807' = Just "\\c"
164+lookupAccent '\783' = Just "\\G"
165+lookupAccent '\777' = Just "\\h"
166+lookupAccent '\803' = Just "\\d"
167+lookupAccent '\785' = Just "\\f"
168+lookupAccent '\778' = Just "\\r"
169+lookupAccent '\865' = Just "\\t"
170+lookupAccent '\782' = Just "\\U"
171+lookupAccent '\780' = Just "\\v"
172+lookupAccent '\774' = Just "\\u"
173+lookupAccent '\808' = Just "\\k"
174+lookupAccent '\8413' = Just "\\textcircled"
175+lookupAccent _ = Nothing
176+
177+toLabel :: PandocMonad m => Text -> LW m Text
178+toLabel z = go `fmap` stringToLaTeX URLString z
179+ where
180+ go = T.concatMap $ \x -> case x of
181+ _ | (isLetter x || isDigit x) && isAscii x -> T.singleton x
182+ | x `elemText` "_-+=:;." -> T.singleton x
183+ | otherwise -> T.pack $ "ux" <> printf "%x" (ord x)
184+
185+-- | Puts contents into LaTeX command.
186+inCmd :: Text -> Doc Text -> Doc Text
187+inCmd cmd contents = char '\\' <> literal cmd <> braces contents
188+
189+mapAlignment :: Text -> Text
190+mapAlignment a = case a of
191+ "top" -> "T"
192+ "top-baseline" -> "t"
193+ "bottom" -> "b"
194+ "center" -> "c"
195+ _ -> a
196+
197+wrapDiv :: PandocMonad m => Attr -> Doc Text -> LW m (Doc Text)
198+wrapDiv (_,classes,kvs) t = do
199+ beamer <- gets stBeamer
200+ let align dir txt = inCmd "begin" dir $$ txt $$ inCmd "end" dir
201+ lang <- toLang $ lookup "lang" kvs
202+ let wrapColumns = if beamer && "columns" `elem` classes
203+ then \contents ->
204+ let valign = maybe "T" mapAlignment (lookup "align" kvs)
205+ totalwidth = maybe [] (\x -> ["totalwidth=" <> x])
206+ (lookup "totalwidth" kvs)
207+ onlytextwidth = filter ("onlytextwidth" ==) classes
208+ options = text $ T.unpack $ T.intercalate "," $
209+ valign : totalwidth ++ onlytextwidth
210+ in inCmd "begin" "columns" <> brackets options
211+ $$ contents
212+ $$ inCmd "end" "columns"
213+ else id
214+ wrapColumn = if beamer && "column" `elem` classes
215+ then \contents ->
216+ let valign =
217+ maybe ""
218+ (brackets . text . T.unpack . mapAlignment)
219+ (lookup "align" kvs)
220+ w = maybe "0.48" fromPct (lookup "width" kvs)
221+ in inCmd "begin" "column" <>
222+ valign <>
223+ braces (literal w <> "\\textwidth")
224+ $$ contents
225+ $$ inCmd "end" "column"
226+ else id
227+ fromPct xs =
228+ case T.unsnoc xs of
229+ Just (ds, '%') -> case safeRead ds of
230+ Just digits -> showFl (digits / 100 :: Double)
231+ Nothing -> xs
232+ _ -> xs
233+ wrapDir = case lookup "dir" kvs of
234+ Just "rtl" -> align "RTL"
235+ Just "ltr" -> align "LTR"
236+ _ -> id
237+ wrapLang txt = case lang of
238+ Just lng -> let (l, o) = toPolyglossiaEnv lng
239+ ops = if T.null o
240+ then ""
241+ else brackets $ literal o
242+ in inCmd "begin" (literal l) <> ops
243+ $$ blankline <> txt <> blankline
244+ $$ inCmd "end" (literal l)
245+ Nothing -> txt
246+ return $ wrapColumns . wrapColumn . wrapDir . wrapLang $ t
247+
248+hypertarget :: PandocMonad m => Bool -> Text -> Doc Text -> LW m (Doc Text)
249+hypertarget _ "" x = return x
250+hypertarget addnewline ident x = do
251+ ref <- literal `fmap` toLabel ident
252+ return $ text "\\hypertarget"
253+ <> braces ref
254+ <> braces ((if addnewline && not (isEmpty x)
255+ then "%" <> cr
256+ else empty) <> x)
257+
258+labelFor :: PandocMonad m => Text -> LW m (Doc Text)
259+labelFor "" = return empty
260+labelFor ident = do
261+ ref <- literal `fmap` toLabel ident
262+ return $ text "\\label" <> braces ref
263+
264+-- Determine listings language from list of class attributes.
265+getListingsLanguage :: [Text] -> Maybe Text
266+getListingsLanguage xs
267+ = foldr ((<|>) . toListingsLanguage) Nothing xs
268+
269+mbBraced :: Text -> Text
270+mbBraced x = if not (T.all isAlphaNum x)
271+ then "{" <> x <> "}"
272+ else x
273+
274+
src/Text/Pandoc/Writers/Markdown.hs+6−591
@@ -22,15 +22,13 @@ module Text.Pandoc.Writers.Markdown (
2222 writePlain) where
2323import Control.Monad.Reader
2424import Control.Monad.State.Strict
25−import Data.Char (isAlphaNum, isDigit)
2625import Data.Default
27−import Data.List (find, intersperse, sortOn, transpose)
26+import Data.List (intersperse, sortOn, transpose)
2827import qualified Data.Map as M
2928import Data.Maybe (fromMaybe, mapMaybe)
3029import qualified Data.Set as Set
3130import Data.Text (Text)
3231import qualified Data.Text as T
33−import Network.HTTP (urlEncode)
3432import Text.HTML.TagSoup (Tag (..), isTagText, parseTags)
3533import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
3634import Text.Pandoc.Definition
@@ -44,59 +42,11 @@ import Text.Pandoc.Templates (renderTemplate)
4442import Text.DocTemplates (Val(..), Context(..), FromContext(..))
4543import Text.Pandoc.Walk
4644import Text.Pandoc.Writers.HTML (writeHtml5String)
47−import Text.Pandoc.Writers.Math (texMathToInlines)
48−import Text.Pandoc.XML (toHtml5Entities)
49−import Data.Coerce (coerce)
50−
51−type Notes = [[Block]]
52−type Ref = (Text, Target, Attr)
53−type Refs = [Ref]
54−
55−type MD m = ReaderT WriterEnv (StateT WriterState m)
56−
57−evalMD :: PandocMonad m => MD m a -> WriterEnv -> WriterState -> m a
58−evalMD md env st = evalStateT (runReaderT md env) st
59−
60−data WriterEnv = WriterEnv { envInList :: Bool
61− , envVariant :: MarkdownVariant
62− , envRefShortcutable :: Bool
63− , envBlockLevel :: Int
64− , envEscapeSpaces :: Bool
65− }
66−
67−data MarkdownVariant =
68− PlainText
69− | Commonmark
70− | Markdown
71− deriving (Show, Eq)
72−
73−instance Default WriterEnv
74− where def = WriterEnv { envInList = False
75− , envVariant = Markdown
76− , envRefShortcutable = True
77− , envBlockLevel = 0
78− , envEscapeSpaces = False
79− }
80−
81−data WriterState = WriterState { stNotes :: Notes
82− , stPrevRefs :: Refs
83− , stRefs :: Refs
84− , stKeys :: M.Map Key
85− (M.Map (Target, Attr) Int)
86− , stLastIdx :: Int
87− , stIds :: Set.Set Text
88− , stNoteNum :: Int
89− }
90−
91−instance Default WriterState
92− where def = WriterState{ stNotes = []
93− , stPrevRefs = []
94− , stRefs = []
95− , stKeys = M.empty
96− , stLastIdx = 0
97− , stIds = Set.empty
98− , stNoteNum = 1
99− }
45+import Text.Pandoc.Writers.Markdown.Inline (inlineListToMarkdown)
46+import Text.Pandoc.Writers.Markdown.Types (MarkdownVariant(..),
47+ WriterState(..),
48+ WriterEnv(..),
49+ Ref, Refs, MD, evalMD)
10050
10151-- | Convert Pandoc to Markdown.
10252writeMarkdown :: PandocMonad m => WriterOptions -> Pandoc -> m Text
@@ -297,49 +247,6 @@ noteToMarkdown opts num blocks = do
297247 then hang (writerTabStop opts) (marker <> spacer) contents
298248 else marker <> spacer <> contents
299249
300−-- | Escape special characters for Markdown.
301−escapeText :: WriterOptions -> Text -> Text
302−escapeText opts = T.pack . go . T.unpack
303− where
304− go [] = []
305− go (c:cs) =
306− case c of
307− '<' | isEnabled Ext_all_symbols_escapable opts ->
308− '\\' : '<' : go cs
309− | otherwise -> "&lt;" ++ go cs
310− '>' | isEnabled Ext_all_symbols_escapable opts ->
311− '\\' : '>' : go cs
312− | otherwise -> "&gt;" ++ go cs
313− '@' | isEnabled Ext_citations opts ->
314− case cs of
315− (d:_)
316− | isAlphaNum d || d == '_'
317− -> '\\':'@':go cs
318− _ -> '@':go cs
319− _ | c `elem` ['\\','`','*','_','[',']','#'] ->
320− '\\':c:go cs
321− '|' | isEnabled Ext_pipe_tables opts -> '\\':'|':go cs
322− '^' | isEnabled Ext_superscript opts -> '\\':'^':go cs
323− '~' | isEnabled Ext_subscript opts ||
324− isEnabled Ext_strikeout opts -> '\\':'~':go cs
325− '$' | isEnabled Ext_tex_math_dollars opts -> '\\':'$':go cs
326− '\'' | isEnabled Ext_smart opts -> '\\':'\'':go cs
327− '"' | isEnabled Ext_smart opts -> '\\':'"':go cs
328− '-' | isEnabled Ext_smart opts ->
329− case cs of
330− '-':_ -> '\\':'-':go cs
331− _ -> '-':go cs
332− '.' | isEnabled Ext_smart opts ->
333− case cs of
334− '.':'.':rest -> '\\':'.':'.':'.':go rest
335− _ -> '.':go cs
336− _ -> case cs of
337− '_':x:xs
338− | isEnabled Ext_intraword_underscores opts
339− , isAlphaNum c
340− , isAlphaNum x -> c : '_' : x : go xs
341− _ -> c : go cs
342−
343250attrsToMarkdown :: Attr -> Doc Text
344251attrsToMarkdown attribs = braces $ hsep [attribId, attribClasses, attribKeys]
345252 where attribId = case attribs of
@@ -912,499 +819,7 @@ blockListToMarkdown opts blocks = do
912819 | otherwise = RawBlock "markdown" "&nbsp;\n"
913820 mconcat <$> mapM (blockToMarkdown opts) (fixBlocks blocks)
914821
915−getKey :: Doc Text -> Key
916−getKey = toKey . render Nothing
917−
918−findUsableIndex :: [Text] -> Int -> Int
919−findUsableIndex lbls i = if tshow i `elem` lbls
920− then findUsableIndex lbls (i + 1)
921− else i
922−
923−getNextIndex :: PandocMonad m => MD m Int
924−getNextIndex = do
925− prevRefs <- gets stPrevRefs
926− refs <- gets stRefs
927− i <- (+ 1) <$> gets stLastIdx
928− modify $ \s -> s{ stLastIdx = i }
929− let refLbls = map (\(r,_,_) -> r) $ prevRefs ++ refs
930− return $ findUsableIndex refLbls i
931−
932−-- | Get reference for target; if none exists, create unique one and return.
933−-- Prefer label if possible; otherwise, generate a unique key.
934−getReference :: PandocMonad m => Attr -> Doc Text -> Target -> MD m Text
935−getReference attr label target = do
936− refs <- gets stRefs
937− case find (\(_,t,a) -> t == target && a == attr) refs of
938− Just (ref, _, _) -> return ref
939− Nothing -> do
940− keys <- gets stKeys
941− let key = getKey label
942− let rawkey = coerce key
943− case M.lookup key keys of
944− Nothing -> do -- no other refs with this label
945− (lab', idx) <- if T.null rawkey ||
946− T.length rawkey > 999 ||
947− T.any (\c -> c == '[' || c == ']') rawkey
948− then do
949− i <- getNextIndex
950− return (tshow i, i)
951− else
952− return (render Nothing label, 0)
953− modify (\s -> s{
954− stRefs = (lab', target, attr) : refs,
955− stKeys = M.insert (getKey label)
956− (M.insert (target, attr) idx mempty)
957− (stKeys s) })
958− return lab'
959−
960− Just km -> -- we have refs with this label
961− case M.lookup (target, attr) km of
962− Just i -> do
963− let lab' = render Nothing $
964− label <> if i == 0
965− then mempty
966− else literal (tshow i)
967− -- make sure it's in stRefs; it may be
968− -- a duplicate that was printed in a previous
969− -- block:
970− when ((lab', target, attr) `notElem` refs) $
971− modify (\s -> s{
972− stRefs = (lab', target, attr) : refs })
973− return lab'
974− Nothing -> do -- but this one is to a new target
975− i <- getNextIndex
976− let lab' = tshow i
977− modify (\s -> s{
978− stRefs = (lab', target, attr) : refs,
979− stKeys = M.insert key
980− (M.insert (target, attr) i km)
981− (stKeys s) })
982− return lab'
983−
984−-- | Convert list of Pandoc inline elements to markdown.
985−inlineListToMarkdown :: PandocMonad m => WriterOptions -> [Inline] -> MD m (Doc Text)
986−inlineListToMarkdown opts lst = do
987− inlist <- asks envInList
988− go (if inlist then avoidBadWrapsInList lst else lst)
989− where go [] = return empty
990− go (x@Math{}:y@(Str t):zs)
991− | T.all isDigit (T.take 1 t) -- starts with digit -- see #7058
992− = liftM2 (<>) (inlineToMarkdown opts x)
993− (go (RawInline (Format "html") "<!-- -->" : y : zs))
994− go (i:is) = case i of
995− Link {} -> case is of
996− -- If a link is followed by another link, or '[', '(' or ':'
997− -- then we don't shortcut
998− Link {}:_ -> unshortcutable
999− Space:Link {}:_ -> unshortcutable
1000− Space:(Str(thead -> Just '[')):_ -> unshortcutable
1001− Space:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
1002− Space:(Cite _ _):_ -> unshortcutable
1003− SoftBreak:Link {}:_ -> unshortcutable
1004− SoftBreak:(Str(thead -> Just '[')):_ -> unshortcutable
1005− SoftBreak:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
1006− SoftBreak:(Cite _ _):_ -> unshortcutable
1007− LineBreak:Link {}:_ -> unshortcutable
1008− LineBreak:(Str(thead -> Just '[')):_ -> unshortcutable
1009− LineBreak:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
1010− LineBreak:(Cite _ _):_ -> unshortcutable
1011− (Cite _ _):_ -> unshortcutable
1012− Str (thead -> Just '['):_ -> unshortcutable
1013− Str (thead -> Just '('):_ -> unshortcutable
1014− Str (thead -> Just ':'):_ -> unshortcutable
1015− (RawInline _ (thead -> Just '[')):_ -> unshortcutable
1016− (RawInline _ (thead -> Just '(')):_ -> unshortcutable
1017− (RawInline _ (thead -> Just ':')):_ -> unshortcutable
1018− (RawInline _ (T.stripPrefix " [" -> Just _ )):_ -> unshortcutable
1019− _ -> shortcutable
1020− _ -> shortcutable
1021− where shortcutable = liftM2 (<>) (inlineToMarkdown opts i) (go is)
1022− unshortcutable = do
1023− iMark <- local
1024− (\env -> env { envRefShortcutable = False })
1025− (inlineToMarkdown opts i)
1026− fmap (iMark <>) (go is)
1027− thead = fmap fst . T.uncons
1028−
1029−isSp :: Inline -> Bool
1030−isSp Space = True
1031−isSp SoftBreak = True
1032−isSp _ = False
1033−
1034−avoidBadWrapsInList :: [Inline] -> [Inline]
1035−avoidBadWrapsInList [] = []
1036−avoidBadWrapsInList (s:Str (T.uncons -> Just ('>',cs)):xs) | isSp s =
1037− Str (" >" <> cs) : avoidBadWrapsInList xs
1038−avoidBadWrapsInList [s, Str (T.uncons -> Just (c, cs))]
1039− | T.null cs && isSp s && c `elem` ['-','*','+'] = [Str $ T.pack [' ', c]]
1040−avoidBadWrapsInList (s:Str (T.uncons -> Just (c, cs)):Space:xs)
1041− | T.null cs && isSp s && c `elem` ['-','*','+'] =
1042− Str (T.pack [' ', c]) : Space : avoidBadWrapsInList xs
1043−avoidBadWrapsInList (s:Str cs:Space:xs)
1044− | isSp s && isOrderedListMarker cs =
1045− Str (" " <> cs) : Space : avoidBadWrapsInList xs
1046−avoidBadWrapsInList [s, Str cs]
1047− | isSp s && isOrderedListMarker cs = [Str $ " " <> cs]
1048−avoidBadWrapsInList (x:xs) = x : avoidBadWrapsInList xs
1049−
1050−isOrderedListMarker :: Text -> Bool
1051−isOrderedListMarker xs = not (T.null xs) && (T.last xs `elem` ['.',')']) &&
1052− isRight (runParser (anyOrderedListMarker >> eof)
1053− defaultParserState "" xs)
1054−
1055−isRight :: Either a b -> Bool
1056−isRight (Right _) = True
1057−isRight (Left _) = False
1058−
1059−-- | Convert Pandoc inline element to markdown.
1060−inlineToMarkdown :: PandocMonad m => WriterOptions -> Inline -> MD m (Doc Text)
1061−inlineToMarkdown opts (Span ("",["emoji"],kvs) [Str s]) =
1062− case lookup "data-emoji" kvs of
1063− Just emojiname | isEnabled Ext_emoji opts ->
1064− return $ ":" <> literal emojiname <> ":"
1065− _ -> inlineToMarkdown opts (Str s)
1066−inlineToMarkdown opts (Span attrs ils) = do
1067− variant <- asks envVariant
1068− contents <- inlineListToMarkdown opts ils
1069− return $ case attrs of
1070− (_,["csl-block"],_) -> (cr <>)
1071− (_,["csl-left-margin"],_) -> (cr <>)
1072− (_,["csl-indent"],_) -> (cr <>)
1073− _ -> id
1074− $ case variant of
1075− PlainText -> contents
1076− _ | attrs == nullAttr -> contents
1077− | isEnabled Ext_bracketed_spans opts ->
1078− let attrs' = if attrs /= nullAttr
1079− then attrsToMarkdown attrs
1080− else empty
1081− in "[" <> contents <> "]" <> attrs'
1082− | isEnabled Ext_raw_html opts ||
1083− isEnabled Ext_native_spans opts ->
1084− tagWithAttrs "span" attrs <> contents <> literal "</span>"
1085− | otherwise -> contents
1086−inlineToMarkdown _ (Emph []) = return empty
1087−inlineToMarkdown opts (Emph lst) = do
1088− variant <- asks envVariant
1089− contents <- inlineListToMarkdown opts lst
1090− return $ case variant of
1091− PlainText
1092− | isEnabled Ext_gutenberg opts -> "_" <> contents <> "_"
1093− | otherwise -> contents
1094− _ -> "*" <> contents <> "*"
1095−inlineToMarkdown _ (Underline []) = return empty
1096−inlineToMarkdown opts (Underline lst) = do
1097− variant <- asks envVariant
1098− contents <- inlineListToMarkdown opts lst
1099− case variant of
1100− PlainText -> return contents
1101− _ | isEnabled Ext_bracketed_spans opts ->
1102− return $ "[" <> contents <> "]" <> "{.ul}"
1103− | isEnabled Ext_native_spans opts ->
1104− return $ tagWithAttrs "span" ("", ["underline"], [])
1105− <> contents
1106− <> literal "</span>"
1107− | isEnabled Ext_raw_html opts ->
1108− return $ "<u>" <> contents <> "</u>"
1109− | otherwise -> inlineToMarkdown opts (Emph lst)
1110−inlineToMarkdown _ (Strong []) = return empty
1111−inlineToMarkdown opts (Strong lst) = do
1112− variant <- asks envVariant
1113− case variant of
1114− PlainText ->
1115− inlineListToMarkdown opts $
1116− if isEnabled Ext_gutenberg opts
1117− then capitalize lst
1118− else lst
1119− _ -> do
1120− contents <- inlineListToMarkdown opts lst
1121− return $ "**" <> contents <> "**"
1122−inlineToMarkdown _ (Strikeout []) = return empty
1123−inlineToMarkdown opts (Strikeout lst) = do
1124− contents <- inlineListToMarkdown opts lst
1125− return $ if isEnabled Ext_strikeout opts
1126− then "~~" <> contents <> "~~"
1127− else if isEnabled Ext_raw_html opts
1128− then "<s>" <> contents <> "</s>"
1129− else contents
1130−inlineToMarkdown _ (Superscript []) = return empty
1131−inlineToMarkdown opts (Superscript lst) =
1132− local (\env -> env {envEscapeSpaces = envVariant env == Markdown}) $ do
1133− contents <- inlineListToMarkdown opts lst
1134− if isEnabled Ext_superscript opts
1135− then return $ "^" <> contents <> "^"
1136− else if isEnabled Ext_raw_html opts
1137− then return $ "<sup>" <> contents <> "</sup>"
1138− else
1139− case traverse toSuperscriptInline lst of
1140− Just xs' | not (writerPreferAscii opts)
1141− -> inlineListToMarkdown opts xs'
1142− _ -> do
1143− let rendered = render Nothing contents
1144− return $
1145− case mapM toSuperscript (T.unpack rendered) of
1146− Just r -> literal $ T.pack r
1147− Nothing -> literal $ "^(" <> rendered <> ")"
1148−inlineToMarkdown _ (Subscript []) = return empty
1149−inlineToMarkdown opts (Subscript lst) =
1150− local (\env -> env {envEscapeSpaces = envVariant env == Markdown}) $ do
1151− contents <- inlineListToMarkdown opts lst
1152− if isEnabled Ext_subscript opts
1153− then return $ "~" <> contents <> "~"
1154− else if isEnabled Ext_raw_html opts
1155− then return $ "<sub>" <> contents <> "</sub>"
1156− else
1157− case traverse toSubscriptInline lst of
1158− Just xs' | not (writerPreferAscii opts)
1159− -> inlineListToMarkdown opts xs'
1160− _ -> do
1161− let rendered = render Nothing contents
1162− return $
1163− case mapM toSuperscript (T.unpack rendered) of
1164− Just r -> literal $ T.pack r
1165− Nothing -> literal $ "_(" <> rendered <> ")"
1166−inlineToMarkdown opts (SmallCaps lst) = do
1167− variant <- asks envVariant
1168− if variant /= PlainText &&
1169− (isEnabled Ext_raw_html opts || isEnabled Ext_native_spans opts)
1170− then inlineToMarkdown opts (Span ("",["smallcaps"],[]) lst)
1171− else inlineListToMarkdown opts $ capitalize lst
1172−inlineToMarkdown opts (Quoted SingleQuote lst) = do
1173− contents <- inlineListToMarkdown opts lst
1174− return $ if isEnabled Ext_smart opts
1175− then "'" <> contents <> "'"
1176− else
1177− if writerPreferAscii opts
1178− then "&lsquo;" <> contents <> "&rsquo;"
1179− else "‘" <> contents <> "’"
1180−inlineToMarkdown opts (Quoted DoubleQuote lst) = do
1181− contents <- inlineListToMarkdown opts lst
1182− return $ if isEnabled Ext_smart opts
1183− then "\"" <> contents <> "\""
1184− else
1185− if writerPreferAscii opts
1186− then "&ldquo;" <> contents <> "&rdquo;"
1187− else "“" <> contents <> "”"
1188−inlineToMarkdown opts (Code attr str) = do
1189− let tickGroups = filter (T.any (== '`')) $ T.group str
1190− let longest = if null tickGroups
1191− then 0
1192− else maximum $ map T.length tickGroups
1193− let marker = T.replicate (longest + 1) "`"
1194− let spacer = if longest == 0 then "" else " "
1195− let attrs = if isEnabled Ext_inline_code_attributes opts && attr /= nullAttr
1196− then attrsToMarkdown attr
1197− else empty
1198− variant <- asks envVariant
1199− case variant of
1200− PlainText -> return $ literal str
1201− _ -> return $ literal
1202− (marker <> spacer <> str <> spacer <> marker) <> attrs
1203−inlineToMarkdown opts (Str str) = do
1204− variant <- asks envVariant
1205− let str' = (if writerPreferAscii opts
1206− then toHtml5Entities
1207− else id) .
1208− (if isEnabled Ext_smart opts
1209− then unsmartify opts
1210− else id) .
1211− (if variant == PlainText
1212− then id
1213− else escapeText opts) $ str
1214− return $ literal str'
1215−inlineToMarkdown opts (Math InlineMath str) =
1216− case writerHTMLMathMethod opts of
1217− WebTeX url -> inlineToMarkdown opts
1218− (Image nullAttr [Str str] (url <> T.pack (urlEncode $ T.unpack str), str))
1219− _ | isEnabled Ext_tex_math_dollars opts ->
1220− return $ "$" <> literal str <> "$"
1221− | isEnabled Ext_tex_math_single_backslash opts ->
1222− return $ "\\(" <> literal str <> "\\)"
1223− | isEnabled Ext_tex_math_double_backslash opts ->
1224− return $ "\\\\(" <> literal str <> "\\\\)"
1225− | otherwise -> do
1226− variant <- asks envVariant
1227− texMathToInlines InlineMath str >>=
1228− inlineListToMarkdown opts .
1229− (if variant == PlainText then makeMathPlainer else id)
1230−inlineToMarkdown opts (Math DisplayMath str) =
1231− case writerHTMLMathMethod opts of
1232− WebTeX url -> (\x -> blankline <> x <> blankline) `fmap`
1233− inlineToMarkdown opts (Image nullAttr [Str str]
1234− (url <> T.pack (urlEncode $ T.unpack str), str))
1235− _ | isEnabled Ext_tex_math_dollars opts ->
1236− return $ "$$" <> literal str <> "$$"
1237− | isEnabled Ext_tex_math_single_backslash opts ->
1238− return $ "\\[" <> literal str <> "\\]"
1239− | isEnabled Ext_tex_math_double_backslash opts ->
1240− return $ "\\\\[" <> literal str <> "\\\\]"
1241− | otherwise -> (\x -> cr <> x <> cr) `fmap`
1242− (texMathToInlines DisplayMath str >>= inlineListToMarkdown opts)
1243−inlineToMarkdown opts il@(RawInline f str) = do
1244− let tickGroups = filter (T.any (== '`')) $ T.group str
1245− let numticks = if null tickGroups
1246− then 1
1247− else 1 + maximum (map T.length tickGroups)
1248− variant <- asks envVariant
1249− let Format fmt = f
1250− let rawAttribInline = return $
1251− literal (T.replicate numticks "`") <> literal str <>
1252− literal (T.replicate numticks "`") <> literal "{=" <> literal fmt <> literal "}"
1253− let renderEmpty = mempty <$ report (InlineNotRendered il)
1254− case variant of
1255− PlainText -> renderEmpty
1256− Commonmark
1257− | f `elem` ["gfm", "commonmark", "commonmark_x", "markdown"]
1258− -> return $ literal str
1259− Markdown
1260− | f `elem` ["markdown", "markdown_github", "markdown_phpextra",
1261− "markdown_mmd", "markdown_strict"]
1262− -> return $ literal str
1263− _ | isEnabled Ext_raw_attribute opts -> rawAttribInline
1264− | f `elem` ["html", "html5", "html4"]
1265− , isEnabled Ext_raw_html opts
1266− -> return $ literal str
1267− | f `elem` ["latex", "tex"]
1268− , isEnabled Ext_raw_tex opts
1269− -> return $ literal str
1270− _ -> renderEmpty
1271−
1272−
1273−inlineToMarkdown opts LineBreak = do
1274− variant <- asks envVariant
1275− if variant == PlainText || isEnabled Ext_hard_line_breaks opts
1276− then return cr
1277− else return $
1278− if isEnabled Ext_escaped_line_breaks opts
1279− then "\\" <> cr
1280− else " " <> cr
1281−inlineToMarkdown _ Space = do
1282− escapeSpaces <- asks envEscapeSpaces
1283− return $ if escapeSpaces then "\\ " else space
1284−inlineToMarkdown opts SoftBreak = do
1285− escapeSpaces <- asks envEscapeSpaces
1286− let space' = if escapeSpaces then "\\ " else space
1287− return $ case writerWrapText opts of
1288− WrapNone -> space'
1289− WrapAuto -> space'
1290− WrapPreserve -> cr
1291−inlineToMarkdown opts (Cite [] lst) = inlineListToMarkdown opts lst
1292−inlineToMarkdown opts (Cite (c:cs) lst)
1293− | not (isEnabled Ext_citations opts) = inlineListToMarkdown opts lst
1294− | otherwise =
1295− if citationMode c == AuthorInText
1296− then do
1297− suffs <- inlineListToMarkdown opts $ citationSuffix c
1298− rest <- mapM convertOne cs
1299− let inbr = suffs <+> joincits rest
1300− br = if isEmpty inbr then empty else char '[' <> inbr <> char ']'
1301− return $ literal ("@" <> citationId c) <+> br
1302− else do
1303− cits <- mapM convertOne (c:cs)
1304− return $ literal "[" <> joincits cits <> literal "]"
1305− where
1306− joincits = hcat . intersperse (literal "; ") . filter (not . isEmpty)
1307− convertOne Citation { citationId = k
1308− , citationPrefix = pinlines
1309− , citationSuffix = sinlines
1310− , citationMode = m }
1311− = do
1312− pdoc <- inlineListToMarkdown opts pinlines
1313− sdoc <- inlineListToMarkdown opts sinlines
1314− let k' = literal (modekey m <> "@" <> k)
1315− r = case sinlines of
1316− Str (T.uncons -> Just (y,_)):_ | y `elem` (",;]@" :: String) -> k' <> sdoc
1317− _ -> k' <+> sdoc
1318− return $ pdoc <+> r
1319− modekey SuppressAuthor = "-"
1320− modekey _ = ""
1321−inlineToMarkdown opts lnk@(Link attr txt (src, tit)) = do
1322− variant <- asks envVariant
1323− linktext <- inlineListToMarkdown opts txt
1324− let linktitle = if T.null tit
1325− then empty
1326− else literal $ " \"" <> tit <> "\""
1327− let srcSuffix = fromMaybe src (T.stripPrefix "mailto:" src)
1328− let useAuto = isURI src &&
1329− case txt of
1330− [Str s] | escapeURI s == srcSuffix -> True
1331− _ -> False
1332− let useRefLinks = writerReferenceLinks opts && not useAuto
1333− shortcutable <- asks envRefShortcutable
1334− let useShortcutRefLinks = shortcutable &&
1335− isEnabled Ext_shortcut_reference_links opts
1336− reftext <- if useRefLinks
1337− then literal <$> getReference attr linktext (src, tit)
1338− else return mempty
1339− case variant of
1340− PlainText
1341− | useAuto -> return $ literal srcSuffix
1342− | otherwise -> return linktext
1343− _ | useAuto -> return $ "<" <> literal srcSuffix <> ">"
1344− | useRefLinks ->
1345− let first = "[" <> linktext <> "]"
1346− second = if getKey linktext == getKey reftext
1347− then if useShortcutRefLinks
1348− then ""
1349− else "[]"
1350− else "[" <> reftext <> "]"
1351− in return $ first <> second
1352− | isEnabled Ext_raw_html opts
1353− , not (isEnabled Ext_link_attributes opts)
1354− , attr /= nullAttr -> -- use raw HTML to render attributes
1355− literal . T.strip <$>
1356− writeHtml5String opts{ writerTemplate = Nothing }
1357− (Pandoc nullMeta [Plain [lnk]])
1358− | otherwise -> return $
1359− "[" <> linktext <> "](" <> literal src <> linktitle <> ")" <>
1360− linkAttributes opts attr
1361−inlineToMarkdown opts img@(Image attr alternate (source, tit))
1362− | isEnabled Ext_raw_html opts &&
1363− not (isEnabled Ext_link_attributes opts) &&
1364− attr /= nullAttr = -- use raw HTML
1365− literal . T.strip <$>
1366− writeHtml5String opts{ writerTemplate = Nothing } (Pandoc nullMeta [Plain [img]])
1367− | otherwise = do
1368− variant <- asks envVariant
1369− let txt = if null alternate || alternate == [Str source]
1370− -- to prevent autolinks
1371− then [Str ""]
1372− else alternate
1373− linkPart <- inlineToMarkdown opts (Link attr txt (source, tit))
1374− return $ case variant of
1375− PlainText -> "[" <> linkPart <> "]"
1376− _ -> "!" <> linkPart
1377−inlineToMarkdown opts (Note contents) = do
1378− modify (\st -> st{ stNotes = contents : stNotes st })
1379− st <- get
1380− let ref = literal $ writerIdentifierPrefix opts <> tshow (stNoteNum st + length (stNotes st) - 1)
1381− if isEnabled Ext_footnotes opts
1382− then return $ "[^" <> ref <> "]"
1383− else return $ "[" <> ref <> "]"
1384−
1385−makeMathPlainer :: [Inline] -> [Inline]
1386−makeMathPlainer = walk go
1387− where
1388− go (Emph xs) = Span nullAttr xs
1389− go x = x
1390−
1391822lineBreakToSpace :: Inline -> Inline
1392823lineBreakToSpace LineBreak = Space
1393824lineBreakToSpace SoftBreak = Space
1394825lineBreakToSpace x = x
1395−
1396−toSubscriptInline :: Inline -> Maybe Inline
1397−toSubscriptInline Space = Just Space
1398−toSubscriptInline (Span attr ils) = Span attr <$> traverse toSubscriptInline ils
1399−toSubscriptInline (Str s) = Str . T.pack <$> traverse toSubscript (T.unpack s)
1400−toSubscriptInline LineBreak = Just LineBreak
1401−toSubscriptInline SoftBreak = Just SoftBreak
1402−toSubscriptInline _ = Nothing
1403−
1404−toSuperscriptInline :: Inline -> Maybe Inline
1405−toSuperscriptInline Space = Just Space
1406−toSuperscriptInline (Span attr ils) = Span attr <$> traverse toSuperscriptInline ils
1407−toSuperscriptInline (Str s) = Str . T.pack <$> traverse toSuperscript (T.unpack s)
1408−toSuperscriptInline LineBreak = Just LineBreak
1409−toSuperscriptInline SoftBreak = Just SoftBreak
1410−toSuperscriptInline _ = Nothing
src/Text/Pandoc/Writers/Markdown/Inline.hsnew file+601
@@ -0,0 +1,601 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+{-# LANGUAGE ViewPatterns #-}
4+{- |
5+ Module : Text.Pandoc.Writers.Markdown.Inline
6+ Copyright : Copyright (C) 2006-2021 John MacFarlane
7+ License : GNU GPL, version 2 or above
8+
9+ Maintainer : John MacFarlane <jgm@berkeley.edu>
10+ Stability : alpha
11+ Portability : portable
12+-}
13+module Text.Pandoc.Writers.Markdown.Inline (
14+ inlineListToMarkdown
15+ ) where
16+import Control.Monad.Reader
17+import Control.Monad.State.Strict
18+import Data.Char (isAlphaNum, isDigit)
19+import Data.List (find, intersperse)
20+import qualified Data.Map as M
21+import Data.Maybe (fromMaybe)
22+import Data.Text (Text)
23+import qualified Data.Text as T
24+import Network.HTTP (urlEncode)
25+import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
26+import Text.Pandoc.Definition
27+import Text.Pandoc.Logging
28+import Text.Pandoc.Options
29+import Text.Pandoc.Parsing hiding (blankline, blanklines, char, space)
30+import Text.DocLayout
31+import Text.Pandoc.Shared
32+import Text.Pandoc.Writers.Shared
33+import Text.Pandoc.Walk
34+import Text.Pandoc.Writers.HTML (writeHtml5String)
35+import Text.Pandoc.Writers.Math (texMathToInlines)
36+import Text.Pandoc.XML (toHtml5Entities)
37+import Data.Coerce (coerce)
38+import Text.Pandoc.Writers.Markdown.Types (MarkdownVariant(..),
39+ WriterState(..),
40+ WriterEnv(..), MD)
41+
42+-- | Escape special characters for Markdown.
43+escapeText :: WriterOptions -> Text -> Text
44+escapeText opts = T.pack . go . T.unpack
45+ where
46+ go [] = []
47+ go (c:cs) =
48+ case c of
49+ '<' | isEnabled Ext_all_symbols_escapable opts ->
50+ '\\' : '<' : go cs
51+ | otherwise -> "&lt;" ++ go cs
52+ '>' | isEnabled Ext_all_symbols_escapable opts ->
53+ '\\' : '>' : go cs
54+ | otherwise -> "&gt;" ++ go cs
55+ '@' | isEnabled Ext_citations opts ->
56+ case cs of
57+ (d:_)
58+ | isAlphaNum d || d == '_'
59+ -> '\\':'@':go cs
60+ _ -> '@':go cs
61+ _ | c `elem` ['\\','`','*','_','[',']','#'] ->
62+ '\\':c:go cs
63+ '|' | isEnabled Ext_pipe_tables opts -> '\\':'|':go cs
64+ '^' | isEnabled Ext_superscript opts -> '\\':'^':go cs
65+ '~' | isEnabled Ext_subscript opts ||
66+ isEnabled Ext_strikeout opts -> '\\':'~':go cs
67+ '$' | isEnabled Ext_tex_math_dollars opts -> '\\':'$':go cs
68+ '\'' | isEnabled Ext_smart opts -> '\\':'\'':go cs
69+ '"' | isEnabled Ext_smart opts -> '\\':'"':go cs
70+ '-' | isEnabled Ext_smart opts ->
71+ case cs of
72+ '-':_ -> '\\':'-':go cs
73+ _ -> '-':go cs
74+ '.' | isEnabled Ext_smart opts ->
75+ case cs of
76+ '.':'.':rest -> '\\':'.':'.':'.':go rest
77+ _ -> '.':go cs
78+ _ -> case cs of
79+ '_':x:xs
80+ | isEnabled Ext_intraword_underscores opts
81+ , isAlphaNum c
82+ , isAlphaNum x -> c : '_' : x : go xs
83+ _ -> c : go cs
84+
85+attrsToMarkdown :: Attr -> Doc Text
86+attrsToMarkdown attribs = braces $ hsep [attribId, attribClasses, attribKeys]
87+ where attribId = case attribs of
88+ ("",_,_) -> empty
89+ (i,_,_) -> "#" <> escAttr i
90+ attribClasses = case attribs of
91+ (_,[],_) -> empty
92+ (_,cs,_) -> hsep $
93+ map (escAttr . ("."<>))
94+ cs
95+ attribKeys = case attribs of
96+ (_,_,[]) -> empty
97+ (_,_,ks) -> hsep $
98+ map (\(k,v) -> escAttr k
99+ <> "=\"" <>
100+ escAttr v <> "\"") ks
101+ escAttr = mconcat . map escAttrChar . T.unpack
102+ escAttrChar '"' = literal "\\\""
103+ escAttrChar '\\' = literal "\\\\"
104+ escAttrChar c = literal $ T.singleton c
105+
106+linkAttributes :: WriterOptions -> Attr -> Doc Text
107+linkAttributes opts attr =
108+ if isEnabled Ext_link_attributes opts && attr /= nullAttr
109+ then attrsToMarkdown attr
110+ else empty
111+
112+getKey :: Doc Text -> Key
113+getKey = toKey . render Nothing
114+
115+findUsableIndex :: [Text] -> Int -> Int
116+findUsableIndex lbls i = if tshow i `elem` lbls
117+ then findUsableIndex lbls (i + 1)
118+ else i
119+
120+getNextIndex :: PandocMonad m => MD m Int
121+getNextIndex = do
122+ prevRefs <- gets stPrevRefs
123+ refs <- gets stRefs
124+ i <- (+ 1) <$> gets stLastIdx
125+ modify $ \s -> s{ stLastIdx = i }
126+ let refLbls = map (\(r,_,_) -> r) $ prevRefs ++ refs
127+ return $ findUsableIndex refLbls i
128+
129+-- | Get reference for target; if none exists, create unique one and return.
130+-- Prefer label if possible; otherwise, generate a unique key.
131+getReference :: PandocMonad m => Attr -> Doc Text -> Target -> MD m Text
132+getReference attr label target = do
133+ refs <- gets stRefs
134+ case find (\(_,t,a) -> t == target && a == attr) refs of
135+ Just (ref, _, _) -> return ref
136+ Nothing -> do
137+ keys <- gets stKeys
138+ let key = getKey label
139+ let rawkey = coerce key
140+ case M.lookup key keys of
141+ Nothing -> do -- no other refs with this label
142+ (lab', idx) <- if T.null rawkey ||
143+ T.length rawkey > 999 ||
144+ T.any (\c -> c == '[' || c == ']') rawkey
145+ then do
146+ i <- getNextIndex
147+ return (tshow i, i)
148+ else
149+ return (render Nothing label, 0)
150+ modify (\s -> s{
151+ stRefs = (lab', target, attr) : refs,
152+ stKeys = M.insert (getKey label)
153+ (M.insert (target, attr) idx mempty)
154+ (stKeys s) })
155+ return lab'
156+
157+ Just km -> -- we have refs with this label
158+ case M.lookup (target, attr) km of
159+ Just i -> do
160+ let lab' = render Nothing $
161+ label <> if i == 0
162+ then mempty
163+ else literal (tshow i)
164+ -- make sure it's in stRefs; it may be
165+ -- a duplicate that was printed in a previous
166+ -- block:
167+ when ((lab', target, attr) `notElem` refs) $
168+ modify (\s -> s{
169+ stRefs = (lab', target, attr) : refs })
170+ return lab'
171+ Nothing -> do -- but this one is to a new target
172+ i <- getNextIndex
173+ let lab' = tshow i
174+ modify (\s -> s{
175+ stRefs = (lab', target, attr) : refs,
176+ stKeys = M.insert key
177+ (M.insert (target, attr) i km)
178+ (stKeys s) })
179+ return lab'
180+
181+-- | Convert list of Pandoc inline elements to markdown.
182+inlineListToMarkdown :: PandocMonad m => WriterOptions -> [Inline] -> MD m (Doc Text)
183+inlineListToMarkdown opts lst = do
184+ inlist <- asks envInList
185+ go (if inlist then avoidBadWrapsInList lst else lst)
186+ where go [] = return empty
187+ go (x@Math{}:y@(Str t):zs)
188+ | T.all isDigit (T.take 1 t) -- starts with digit -- see #7058
189+ = liftM2 (<>) (inlineToMarkdown opts x)
190+ (go (RawInline (Format "html") "<!-- -->" : y : zs))
191+ go (i:is) = case i of
192+ Link {} -> case is of
193+ -- If a link is followed by another link, or '[', '(' or ':'
194+ -- then we don't shortcut
195+ Link {}:_ -> unshortcutable
196+ Space:Link {}:_ -> unshortcutable
197+ Space:(Str(thead -> Just '[')):_ -> unshortcutable
198+ Space:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
199+ Space:(Cite _ _):_ -> unshortcutable
200+ SoftBreak:Link {}:_ -> unshortcutable
201+ SoftBreak:(Str(thead -> Just '[')):_ -> unshortcutable
202+ SoftBreak:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
203+ SoftBreak:(Cite _ _):_ -> unshortcutable
204+ LineBreak:Link {}:_ -> unshortcutable
205+ LineBreak:(Str(thead -> Just '[')):_ -> unshortcutable
206+ LineBreak:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
207+ LineBreak:(Cite _ _):_ -> unshortcutable
208+ (Cite _ _):_ -> unshortcutable
209+ Str (thead -> Just '['):_ -> unshortcutable
210+ Str (thead -> Just '('):_ -> unshortcutable
211+ Str (thead -> Just ':'):_ -> unshortcutable
212+ (RawInline _ (thead -> Just '[')):_ -> unshortcutable
213+ (RawInline _ (thead -> Just '(')):_ -> unshortcutable
214+ (RawInline _ (thead -> Just ':')):_ -> unshortcutable
215+ (RawInline _ (T.stripPrefix " [" -> Just _ )):_ -> unshortcutable
216+ _ -> shortcutable
217+ _ -> shortcutable
218+ where shortcutable = liftM2 (<>) (inlineToMarkdown opts i) (go is)
219+ unshortcutable = do
220+ iMark <- local
221+ (\env -> env { envRefShortcutable = False })
222+ (inlineToMarkdown opts i)
223+ fmap (iMark <>) (go is)
224+ thead = fmap fst . T.uncons
225+
226+isSp :: Inline -> Bool
227+isSp Space = True
228+isSp SoftBreak = True
229+isSp _ = False
230+
231+avoidBadWrapsInList :: [Inline] -> [Inline]
232+avoidBadWrapsInList [] = []
233+avoidBadWrapsInList (s:Str (T.uncons -> Just ('>',cs)):xs) | isSp s =
234+ Str (" >" <> cs) : avoidBadWrapsInList xs
235+avoidBadWrapsInList [s, Str (T.uncons -> Just (c, cs))]
236+ | T.null cs && isSp s && c `elem` ['-','*','+'] = [Str $ T.pack [' ', c]]
237+avoidBadWrapsInList (s:Str (T.uncons -> Just (c, cs)):Space:xs)
238+ | T.null cs && isSp s && c `elem` ['-','*','+'] =
239+ Str (T.pack [' ', c]) : Space : avoidBadWrapsInList xs
240+avoidBadWrapsInList (s:Str cs:Space:xs)
241+ | isSp s && isOrderedListMarker cs =
242+ Str (" " <> cs) : Space : avoidBadWrapsInList xs
243+avoidBadWrapsInList [s, Str cs]
244+ | isSp s && isOrderedListMarker cs = [Str $ " " <> cs]
245+avoidBadWrapsInList (x:xs) = x : avoidBadWrapsInList xs
246+
247+isOrderedListMarker :: Text -> Bool
248+isOrderedListMarker xs = not (T.null xs) && (T.last xs `elem` ['.',')']) &&
249+ isRight (runParser (anyOrderedListMarker >> eof)
250+ defaultParserState "" xs)
251+ where
252+ isRight (Right _) = True
253+ isRight (Left _) = False
254+
255+-- | Convert Pandoc inline element to markdown.
256+inlineToMarkdown :: PandocMonad m => WriterOptions -> Inline -> MD m (Doc Text)
257+inlineToMarkdown opts (Span ("",["emoji"],kvs) [Str s]) =
258+ case lookup "data-emoji" kvs of
259+ Just emojiname | isEnabled Ext_emoji opts ->
260+ return $ ":" <> literal emojiname <> ":"
261+ _ -> inlineToMarkdown opts (Str s)
262+inlineToMarkdown opts (Span attrs ils) = do
263+ variant <- asks envVariant
264+ contents <- inlineListToMarkdown opts ils
265+ return $ case attrs of
266+ (_,["csl-block"],_) -> (cr <>)
267+ (_,["csl-left-margin"],_) -> (cr <>)
268+ (_,["csl-indent"],_) -> (cr <>)
269+ _ -> id
270+ $ case variant of
271+ PlainText -> contents
272+ _ | attrs == nullAttr -> contents
273+ | isEnabled Ext_bracketed_spans opts ->
274+ let attrs' = if attrs /= nullAttr
275+ then attrsToMarkdown attrs
276+ else empty
277+ in "[" <> contents <> "]" <> attrs'
278+ | isEnabled Ext_raw_html opts ||
279+ isEnabled Ext_native_spans opts ->
280+ tagWithAttrs "span" attrs <> contents <> literal "</span>"
281+ | otherwise -> contents
282+inlineToMarkdown _ (Emph []) = return empty
283+inlineToMarkdown opts (Emph lst) = do
284+ variant <- asks envVariant
285+ contents <- inlineListToMarkdown opts lst
286+ return $ case variant of
287+ PlainText
288+ | isEnabled Ext_gutenberg opts -> "_" <> contents <> "_"
289+ | otherwise -> contents
290+ _ -> "*" <> contents <> "*"
291+inlineToMarkdown _ (Underline []) = return empty
292+inlineToMarkdown opts (Underline lst) = do
293+ variant <- asks envVariant
294+ contents <- inlineListToMarkdown opts lst
295+ case variant of
296+ PlainText -> return contents
297+ _ | isEnabled Ext_bracketed_spans opts ->
298+ return $ "[" <> contents <> "]" <> "{.ul}"
299+ | isEnabled Ext_native_spans opts ->
300+ return $ tagWithAttrs "span" ("", ["underline"], [])
301+ <> contents
302+ <> literal "</span>"
303+ | isEnabled Ext_raw_html opts ->
304+ return $ "<u>" <> contents <> "</u>"
305+ | otherwise -> inlineToMarkdown opts (Emph lst)
306+inlineToMarkdown _ (Strong []) = return empty
307+inlineToMarkdown opts (Strong lst) = do
308+ variant <- asks envVariant
309+ case variant of
310+ PlainText ->
311+ inlineListToMarkdown opts $
312+ if isEnabled Ext_gutenberg opts
313+ then capitalize lst
314+ else lst
315+ _ -> do
316+ contents <- inlineListToMarkdown opts lst
317+ return $ "**" <> contents <> "**"
318+inlineToMarkdown _ (Strikeout []) = return empty
319+inlineToMarkdown opts (Strikeout lst) = do
320+ contents <- inlineListToMarkdown opts lst
321+ return $ if isEnabled Ext_strikeout opts
322+ then "~~" <> contents <> "~~"
323+ else if isEnabled Ext_raw_html opts
324+ then "<s>" <> contents <> "</s>"
325+ else contents
326+inlineToMarkdown _ (Superscript []) = return empty
327+inlineToMarkdown opts (Superscript lst) =
328+ local (\env -> env {envEscapeSpaces = envVariant env == Markdown}) $ do
329+ contents <- inlineListToMarkdown opts lst
330+ if isEnabled Ext_superscript opts
331+ then return $ "^" <> contents <> "^"
332+ else if isEnabled Ext_raw_html opts
333+ then return $ "<sup>" <> contents <> "</sup>"
334+ else
335+ case traverse toSuperscriptInline lst of
336+ Just xs' | not (writerPreferAscii opts)
337+ -> inlineListToMarkdown opts xs'
338+ _ -> do
339+ let rendered = render Nothing contents
340+ return $
341+ case mapM toSuperscript (T.unpack rendered) of
342+ Just r -> literal $ T.pack r
343+ Nothing -> literal $ "^(" <> rendered <> ")"
344+inlineToMarkdown _ (Subscript []) = return empty
345+inlineToMarkdown opts (Subscript lst) =
346+ local (\env -> env {envEscapeSpaces = envVariant env == Markdown}) $ do
347+ contents <- inlineListToMarkdown opts lst
348+ if isEnabled Ext_subscript opts
349+ then return $ "~" <> contents <> "~"
350+ else if isEnabled Ext_raw_html opts
351+ then return $ "<sub>" <> contents <> "</sub>"
352+ else
353+ case traverse toSubscriptInline lst of
354+ Just xs' | not (writerPreferAscii opts)
355+ -> inlineListToMarkdown opts xs'
356+ _ -> do
357+ let rendered = render Nothing contents
358+ return $
359+ case mapM toSuperscript (T.unpack rendered) of
360+ Just r -> literal $ T.pack r
361+ Nothing -> literal $ "_(" <> rendered <> ")"
362+inlineToMarkdown opts (SmallCaps lst) = do
363+ variant <- asks envVariant
364+ if variant /= PlainText &&
365+ (isEnabled Ext_raw_html opts || isEnabled Ext_native_spans opts)
366+ then inlineToMarkdown opts (Span ("",["smallcaps"],[]) lst)
367+ else inlineListToMarkdown opts $ capitalize lst
368+inlineToMarkdown opts (Quoted SingleQuote lst) = do
369+ contents <- inlineListToMarkdown opts lst
370+ return $ if isEnabled Ext_smart opts
371+ then "'" <> contents <> "'"
372+ else
373+ if writerPreferAscii opts
374+ then "&lsquo;" <> contents <> "&rsquo;"
375+ else "‘" <> contents <> "’"
376+inlineToMarkdown opts (Quoted DoubleQuote lst) = do
377+ contents <- inlineListToMarkdown opts lst
378+ return $ if isEnabled Ext_smart opts
379+ then "\"" <> contents <> "\""
380+ else
381+ if writerPreferAscii opts
382+ then "&ldquo;" <> contents <> "&rdquo;"
383+ else "“" <> contents <> "”"
384+inlineToMarkdown opts (Code attr str) = do
385+ let tickGroups = filter (T.any (== '`')) $ T.group str
386+ let longest = if null tickGroups
387+ then 0
388+ else maximum $ map T.length tickGroups
389+ let marker = T.replicate (longest + 1) "`"
390+ let spacer = if longest == 0 then "" else " "
391+ let attrs = if isEnabled Ext_inline_code_attributes opts && attr /= nullAttr
392+ then attrsToMarkdown attr
393+ else empty
394+ variant <- asks envVariant
395+ case variant of
396+ PlainText -> return $ literal str
397+ _ -> return $ literal
398+ (marker <> spacer <> str <> spacer <> marker) <> attrs
399+inlineToMarkdown opts (Str str) = do
400+ variant <- asks envVariant
401+ let str' = (if writerPreferAscii opts
402+ then toHtml5Entities
403+ else id) .
404+ (if isEnabled Ext_smart opts
405+ then unsmartify opts
406+ else id) .
407+ (if variant == PlainText
408+ then id
409+ else escapeText opts) $ str
410+ return $ literal str'
411+inlineToMarkdown opts (Math InlineMath str) =
412+ case writerHTMLMathMethod opts of
413+ WebTeX url -> inlineToMarkdown opts
414+ (Image nullAttr [Str str] (url <> T.pack (urlEncode $ T.unpack str), str))
415+ _ | isEnabled Ext_tex_math_dollars opts ->
416+ return $ "$" <> literal str <> "$"
417+ | isEnabled Ext_tex_math_single_backslash opts ->
418+ return $ "\\(" <> literal str <> "\\)"
419+ | isEnabled Ext_tex_math_double_backslash opts ->
420+ return $ "\\\\(" <> literal str <> "\\\\)"
421+ | otherwise -> do
422+ variant <- asks envVariant
423+ texMathToInlines InlineMath str >>=
424+ inlineListToMarkdown opts .
425+ (if variant == PlainText then makeMathPlainer else id)
426+inlineToMarkdown opts (Math DisplayMath str) =
427+ case writerHTMLMathMethod opts of
428+ WebTeX url -> (\x -> blankline <> x <> blankline) `fmap`
429+ inlineToMarkdown opts (Image nullAttr [Str str]
430+ (url <> T.pack (urlEncode $ T.unpack str), str))
431+ _ | isEnabled Ext_tex_math_dollars opts ->
432+ return $ "$$" <> literal str <> "$$"
433+ | isEnabled Ext_tex_math_single_backslash opts ->
434+ return $ "\\[" <> literal str <> "\\]"
435+ | isEnabled Ext_tex_math_double_backslash opts ->
436+ return $ "\\\\[" <> literal str <> "\\\\]"
437+ | otherwise -> (\x -> cr <> x <> cr) `fmap`
438+ (texMathToInlines DisplayMath str >>= inlineListToMarkdown opts)
439+inlineToMarkdown opts il@(RawInline f str) = do
440+ let tickGroups = filter (T.any (== '`')) $ T.group str
441+ let numticks = if null tickGroups
442+ then 1
443+ else 1 + maximum (map T.length tickGroups)
444+ variant <- asks envVariant
445+ let Format fmt = f
446+ let rawAttribInline = return $
447+ literal (T.replicate numticks "`") <> literal str <>
448+ literal (T.replicate numticks "`") <> literal "{=" <> literal fmt <> literal "}"
449+ let renderEmpty = mempty <$ report (InlineNotRendered il)
450+ case variant of
451+ PlainText -> renderEmpty
452+ Commonmark
453+ | f `elem` ["gfm", "commonmark", "commonmark_x", "markdown"]
454+ -> return $ literal str
455+ Markdown
456+ | f `elem` ["markdown", "markdown_github", "markdown_phpextra",
457+ "markdown_mmd", "markdown_strict"]
458+ -> return $ literal str
459+ _ | isEnabled Ext_raw_attribute opts -> rawAttribInline
460+ | f `elem` ["html", "html5", "html4"]
461+ , isEnabled Ext_raw_html opts
462+ -> return $ literal str
463+ | f `elem` ["latex", "tex"]
464+ , isEnabled Ext_raw_tex opts
465+ -> return $ literal str
466+ _ -> renderEmpty
467+
468+
469+inlineToMarkdown opts LineBreak = do
470+ variant <- asks envVariant
471+ if variant == PlainText || isEnabled Ext_hard_line_breaks opts
472+ then return cr
473+ else return $
474+ if isEnabled Ext_escaped_line_breaks opts
475+ then "\\" <> cr
476+ else " " <> cr
477+inlineToMarkdown _ Space = do
478+ escapeSpaces <- asks envEscapeSpaces
479+ return $ if escapeSpaces then "\\ " else space
480+inlineToMarkdown opts SoftBreak = do
481+ escapeSpaces <- asks envEscapeSpaces
482+ let space' = if escapeSpaces then "\\ " else space
483+ return $ case writerWrapText opts of
484+ WrapNone -> space'
485+ WrapAuto -> space'
486+ WrapPreserve -> cr
487+inlineToMarkdown opts (Cite [] lst) = inlineListToMarkdown opts lst
488+inlineToMarkdown opts (Cite (c:cs) lst)
489+ | not (isEnabled Ext_citations opts) = inlineListToMarkdown opts lst
490+ | otherwise =
491+ if citationMode c == AuthorInText
492+ then do
493+ suffs <- inlineListToMarkdown opts $ citationSuffix c
494+ rest <- mapM convertOne cs
495+ let inbr = suffs <+> joincits rest
496+ br = if isEmpty inbr then empty else char '[' <> inbr <> char ']'
497+ return $ literal ("@" <> citationId c) <+> br
498+ else do
499+ cits <- mapM convertOne (c:cs)
500+ return $ literal "[" <> joincits cits <> literal "]"
501+ where
502+ joincits = hcat . intersperse (literal "; ") . filter (not . isEmpty)
503+ convertOne Citation { citationId = k
504+ , citationPrefix = pinlines
505+ , citationSuffix = sinlines
506+ , citationMode = m }
507+ = do
508+ pdoc <- inlineListToMarkdown opts pinlines
509+ sdoc <- inlineListToMarkdown opts sinlines
510+ let k' = literal (modekey m <> "@" <> k)
511+ r = case sinlines of
512+ Str (T.uncons -> Just (y,_)):_ | y `elem` (",;]@" :: String) -> k' <> sdoc
513+ _ -> k' <+> sdoc
514+ return $ pdoc <+> r
515+ modekey SuppressAuthor = "-"
516+ modekey _ = ""
517+inlineToMarkdown opts lnk@(Link attr txt (src, tit)) = do
518+ variant <- asks envVariant
519+ linktext <- inlineListToMarkdown opts txt
520+ let linktitle = if T.null tit
521+ then empty
522+ else literal $ " \"" <> tit <> "\""
523+ let srcSuffix = fromMaybe src (T.stripPrefix "mailto:" src)
524+ let useAuto = isURI src &&
525+ case txt of
526+ [Str s] | escapeURI s == srcSuffix -> True
527+ _ -> False
528+ let useRefLinks = writerReferenceLinks opts && not useAuto
529+ shortcutable <- asks envRefShortcutable
530+ let useShortcutRefLinks = shortcutable &&
531+ isEnabled Ext_shortcut_reference_links opts
532+ reftext <- if useRefLinks
533+ then literal <$> getReference attr linktext (src, tit)
534+ else return mempty
535+ case variant of
536+ PlainText
537+ | useAuto -> return $ literal srcSuffix
538+ | otherwise -> return linktext
539+ _ | useAuto -> return $ "<" <> literal srcSuffix <> ">"
540+ | useRefLinks ->
541+ let first = "[" <> linktext <> "]"
542+ second = if getKey linktext == getKey reftext
543+ then if useShortcutRefLinks
544+ then ""
545+ else "[]"
546+ else "[" <> reftext <> "]"
547+ in return $ first <> second
548+ | isEnabled Ext_raw_html opts
549+ , not (isEnabled Ext_link_attributes opts)
550+ , attr /= nullAttr -> -- use raw HTML to render attributes
551+ literal . T.strip <$>
552+ writeHtml5String opts{ writerTemplate = Nothing }
553+ (Pandoc nullMeta [Plain [lnk]])
554+ | otherwise -> return $
555+ "[" <> linktext <> "](" <> literal src <> linktitle <> ")" <>
556+ linkAttributes opts attr
557+inlineToMarkdown opts img@(Image attr alternate (source, tit))
558+ | isEnabled Ext_raw_html opts &&
559+ not (isEnabled Ext_link_attributes opts) &&
560+ attr /= nullAttr = -- use raw HTML
561+ literal . T.strip <$>
562+ writeHtml5String opts{ writerTemplate = Nothing } (Pandoc nullMeta [Plain [img]])
563+ | otherwise = do
564+ variant <- asks envVariant
565+ let txt = if null alternate || alternate == [Str source]
566+ -- to prevent autolinks
567+ then [Str ""]
568+ else alternate
569+ linkPart <- inlineToMarkdown opts (Link attr txt (source, tit))
570+ return $ case variant of
571+ PlainText -> "[" <> linkPart <> "]"
572+ _ -> "!" <> linkPart
573+inlineToMarkdown opts (Note contents) = do
574+ modify (\st -> st{ stNotes = contents : stNotes st })
575+ st <- get
576+ let ref = literal $ writerIdentifierPrefix opts <> tshow (stNoteNum st + length (stNotes st) - 1)
577+ if isEnabled Ext_footnotes opts
578+ then return $ "[^" <> ref <> "]"
579+ else return $ "[" <> ref <> "]"
580+
581+makeMathPlainer :: [Inline] -> [Inline]
582+makeMathPlainer = walk go
583+ where
584+ go (Emph xs) = Span nullAttr xs
585+ go x = x
586+
587+toSubscriptInline :: Inline -> Maybe Inline
588+toSubscriptInline Space = Just Space
589+toSubscriptInline (Span attr ils) = Span attr <$> traverse toSubscriptInline ils
590+toSubscriptInline (Str s) = Str . T.pack <$> traverse toSubscript (T.unpack s)
591+toSubscriptInline LineBreak = Just LineBreak
592+toSubscriptInline SoftBreak = Just SoftBreak
593+toSubscriptInline _ = Nothing
594+
595+toSuperscriptInline :: Inline -> Maybe Inline
596+toSuperscriptInline Space = Just Space
597+toSuperscriptInline (Span attr ils) = Span attr <$> traverse toSuperscriptInline ils
598+toSuperscriptInline (Str s) = Str . T.pack <$> traverse toSuperscript (T.unpack s)
599+toSuperscriptInline LineBreak = Just LineBreak
600+toSuperscriptInline SoftBreak = Just SoftBreak
601+toSuperscriptInline _ = Nothing
src/Text/Pandoc/Writers/Markdown/Types.hsnew file+81
@@ -0,0 +1,81 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{- |
3+ Module : Text.Pandoc.Writers.Markdown.Types
4+ Copyright : Copyright (C) 2006-2021 John MacFarlane
5+ License : GNU GPL, version 2 or above
6+
7+ Maintainer : John MacFarlane <jgm@berkeley.edu>
8+ Stability : alpha
9+ Portability : portable
10+-}
11+module Text.Pandoc.Writers.Markdown.Types (
12+ MarkdownVariant(..),
13+ WriterState(..),
14+ WriterEnv(..),
15+ Notes,
16+ Ref,
17+ Refs,
18+ MD,
19+ evalMD
20+ ) where
21+import Control.Monad.Reader
22+import Control.Monad.State.Strict
23+import Data.Default
24+import qualified Data.Map as M
25+import qualified Data.Set as Set
26+import Data.Text (Text)
27+import Text.Pandoc.Parsing (Key)
28+import Text.Pandoc.Class.PandocMonad (PandocMonad)
29+import Text.Pandoc.Definition
30+
31+type Notes = [[Block]]
32+type Ref = (Text, Target, Attr)
33+type Refs = [Ref]
34+
35+type MD m = ReaderT WriterEnv (StateT WriterState m)
36+
37+evalMD :: PandocMonad m => MD m a -> WriterEnv -> WriterState -> m a
38+evalMD md env st = evalStateT (runReaderT md env) st
39+
40+data WriterEnv = WriterEnv { envInList :: Bool
41+ , envVariant :: MarkdownVariant
42+ , envRefShortcutable :: Bool
43+ , envBlockLevel :: Int
44+ , envEscapeSpaces :: Bool
45+ }
46+
47+data MarkdownVariant =
48+ PlainText
49+ | Commonmark
50+ | Markdown
51+ deriving (Show, Eq)
52+
53+instance Default WriterEnv
54+ where def = WriterEnv { envInList = False
55+ , envVariant = Markdown
56+ , envRefShortcutable = True
57+ , envBlockLevel = 0
58+ , envEscapeSpaces = False
59+ }
60+
61+data WriterState = WriterState { stNotes :: Notes
62+ , stPrevRefs :: Refs
63+ , stRefs :: Refs
64+ , stKeys :: M.Map Key
65+ (M.Map (Target, Attr) Int)
66+ , stLastIdx :: Int
67+ , stIds :: Set.Set Text
68+ , stNoteNum :: Int
69+ }
70+
71+instance Default WriterState
72+ where def = WriterState{ stNotes = []
73+ , stPrevRefs = []
74+ , stRefs = []
75+ , stKeys = M.empty
76+ , stLastIdx = 0
77+ , stIds = Set.empty
78+ , stNoteNum = 1
79+ }
80+
81+
stack.yaml+1−1
@@ -12,7 +12,7 @@ extra-deps:
1212- commonmark-extensions-0.2.0.4
1313- commonmark-pandoc-0.2.0.1
1414- tasty-bench-0.2.1
15−- citeproc-0.3.0.7
15+- citeproc-0.3.0.8
1616- skylighting-0.10.4
1717- skylighting-core-0.10.4
1818- xml-conduit-1.9.1.0
trypandoc/trypandoc.hs−4
@@ -14,16 +14,12 @@ module Main where
1414import Network.Wai.Handler.CGI
1515import Network.Wai.Middleware.Timeout (timeout)
1616import Network.Wai
17−import Control.Applicative ((<$>))
1817import Data.Maybe (fromMaybe)
1918import Network.HTTP.Types.Status (status200)
2019import Network.HTTP.Types.Header (hContentType)
2120import Network.HTTP.Types.URI (queryToQueryText)
2221import Text.Pandoc
23−import Text.Pandoc.Writers.Math (defaultMathJaxURL)
2422import Text.Pandoc.Highlighting (pygments)
25−import Text.Pandoc.Readers (getReader, Reader(..))
26−import Text.Pandoc.Writers (getWriter, Writer(..))
2723import Text.Pandoc.Shared (tabFilter)
2824import Data.Aeson
2925import qualified Data.Text as T