git.iko.soy/typeable/pandoc
on master help sign in
Implement `--ascii` (`writerPreferAscii`) in writers, not App.

Now the `write*` functions for Docbook, HTML, ICML, JATS, Man, Ms, OPML are sensitive to `writerPreferAscii`. Previously the to-ascii translation was done in Text.Pandoc.App, and thus not available to those using the writer functions directly. In addition, the LaTeX writer is now sensitive to `writerPreferAscii` and to `--ascii`. 100% ASCII output can't be guaranteed, but the writer will use commands like `\"{a}` and `\l` whenever possible, to avoid emiting a non-ASCII character. A new unexported module, Text.Pandoc.Groff, has been added to store functions used in the different groff-based writers.

commit36f1846cc3130dbe4168789cc03f916ebf5828c8 authorJohn MacFarlane <jgm@berkeley.edu> dateSun 30 Sep 2018, 22:32 (8 years ago) parent0a8d212
13 files changed+230−76
MANUAL.txt+4−2 pandoc.cabal+1 src/Text/Pandoc/App.hs+5−20 src/Text/Pandoc/Groff.hs+43 src/Text/Pandoc/Writers/Docbook.hs+4−3 src/Text/Pandoc/Writers/HTML.hs+11−7 src/Text/Pandoc/Writers/ICML.hs+2−1 src/Text/Pandoc/Writers/JATS.hs+2−1 src/Text/Pandoc/Writers/LaTeX.hs+105−39 src/Text/Pandoc/Writers/Man.hs+3−1 src/Text/Pandoc/Writers/Ms.hs+3−1 src/Text/Pandoc/Writers/OPML.hs+2−1 test/command/ascii.md+45
MANUAL.txt+4−2
@@ -860,8 +860,10 @@ Options affecting specific writers {.options}
860860
861861: Use only ASCII characters in output. Currently supported for
862862 XML and HTML formats (which use numerical entities instead of
863− UTF-8 when this option is selected) and for groff ms and man
864− (which use hexadecimal escapes).
863+ UTF-8 when this option is selected), groff ms and man
864+ (which use hexadecimal escapes), and to a limited degree
865+ for LaTeX (which uses standard commands for accented
866+ characters when possible).
865867
866868`--reference-links`
867869
pandoc.cabal+1
@@ -540,6 +540,7 @@ library
540540 Text.Pandoc.Lua.Packages,
541541 Text.Pandoc.Lua.StackInstances,
542542 Text.Pandoc.Lua.Util,
543+ Text.Pandoc.Groff
543544 Text.Pandoc.CSS,
544545 Text.Pandoc.CSV,
545546 Text.Pandoc.UUID,
src/Text/Pandoc/App.hs+5−20
@@ -52,7 +52,7 @@ import Data.Aeson (defaultOptions)
5252import Data.Aeson.TH (deriveJSON)
5353import qualified Data.ByteString as BS
5454import qualified Data.ByteString.Lazy as B
55−import Data.Char (toLower, toUpper, isAscii, ord)
55+import Data.Char (toLower, toUpper)
5656import Data.List (find, intercalate, isPrefixOf, isSuffixOf, sort)
5757import qualified Data.Map as M
5858import Data.Maybe (fromMaybe, isJust, isNothing)
@@ -95,7 +95,6 @@ import Text.Pandoc.Shared (eastAsianLineBreakFilter, stripEmptyParagraphs,
9595 headerShift, isURI, ordNub, safeRead, tabFilter, uriPathToPath)
9696import qualified Text.Pandoc.UTF8 as UTF8
9797import Text.Pandoc.Writers.Math (defaultKaTeXURL, defaultMathJaxURL)
98−import Text.Pandoc.XML (toEntities)
9998import Text.Printf
10099#ifndef _WINDOWS
101100import System.Posix.IO (stdOutput)
@@ -443,6 +442,7 @@ convertWithOpts opts = do
443442 , writerTOCDepth = optTOCDepth opts
444443 , writerReferenceDoc = optReferenceDoc opts
445444 , writerSyntaxMap = syntaxMap
445+ , writerPreferAscii = optAscii opts
446446 }
447447
448448 let readerOpts = def{
@@ -519,19 +519,10 @@ convertWithOpts opts = do
519519 let htmlFormat = format `elem`
520520 ["html","html4","html5","s5","slidy",
521521 "slideous","dzslides","revealjs"]
522− escape
523− | optAscii opts
524− , htmlFormat || format == "docbook4" ||
525− format == "docbook5" || format == "docbook" ||
526− format == "jats" || format == "opml" ||
527− format == "icml" = toEntities
528− | optAscii opts
529− , format == "ms" || format == "man" = groffEscape
530− | otherwise = id
531522 addNl = if standalone
532523 then id
533524 else (<> T.singleton '\n')
534− output <- (addNl . escape) <$> f writerOptions doc
525+ output <- addNl <$> f writerOptions doc
535526 writerFn eol outputFile =<<
536527 if optSelfContained opts && htmlFormat
537528 -- TODO not maximally efficient; change type
@@ -539,12 +530,6 @@ convertWithOpts opts = do
539530 then T.pack <$> makeSelfContained (T.unpack output)
540531 else return output
541532
542−groffEscape :: Text -> Text
543−groffEscape = T.concatMap toUchar
544− where toUchar c
545− | isAscii c = T.singleton c
546− | otherwise = T.pack $ printf "\\[u%04X]" (ord c)
547−
548533type Transform = Pandoc -> Pandoc
549534
550535isTextFormat :: String -> Bool
@@ -606,7 +591,7 @@ data Opt = Opt
606591 , optPdfEngineArgs :: [String] -- ^ Flags to pass to the engine
607592 , optSlideLevel :: Maybe Int -- ^ Header level that creates slides
608593 , optSetextHeaders :: Bool -- ^ Use atx headers for markdown level 1-2
609− , optAscii :: Bool -- ^ Use ascii characters only in html
594+ , optAscii :: Bool -- ^ Prefer ascii output
610595 , optDefaultImageExtension :: String -- ^ Default image extension
611596 , optExtractMedia :: Maybe FilePath -- ^ Path to extract embedded media
612597 , optTrackChanges :: TrackChanges -- ^ Accept or reject MS Word track-changes.
@@ -1173,7 +1158,7 @@ options =
11731158 , Option "" ["ascii"]
11741159 (NoArg
11751160 (\opt -> return opt { optAscii = True }))
1176− "" -- "Use ascii characters only in HTML output"
1161+ "" -- "Prefer ASCII output"
11771162
11781163 , Option "" ["reference-links"]
11791164 (NoArg
src/Text/Pandoc/Groff.hsnew file+43
@@ -0,0 +1,43 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+{-
3+Copyright (C) 2018 John MacFarlane <jgm@berkeley.edu>
4+
5+This program is free software; you can redistribute it and/or modify
6+it under the terms of the GNU General Public License as published by
7+the Free Software Foundation; either version 2 of the License, or
8+(at your option) any later version.
9+
10+This program is distributed in the hope that it will be useful,
11+but WITHOUT ANY WARRANTY; without even the implied warranty of
12+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+GNU General Public License for more details.
14+
15+You should have received a copy of the GNU General Public License
16+along with this program; if not, write to the Free Software
17+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18+-}
19+
20+{- |
21+ Module : Text.Pandoc.Groff
22+ Copyright : Copyright (C) 2018 John MacFarlane
23+ License : GNU GPL, version 2 or above
24+
25+ Maintainer : John MacFarlane <jgm@berkeley.edu>
26+ Stability : alpha
27+ Portability : portable
28+
29+Shared functions for escaping and formatting groff.
30+-}
31+module Text.Pandoc.Groff ( groffEscape )
32+where
33+
34+import Prelude
35+import Data.Char (isAscii, ord)
36+import qualified Data.Text as T
37+import Text.Printf (printf)
38+
39+groffEscape :: T.Text -> T.Text
40+groffEscape = T.concatMap toUchar
41+ where toUchar c
42+ | isAscii c = T.singleton c
43+ | otherwise = T.pack $ printf "\\[u%04X]" (ord c)
src/Text/Pandoc/Writers/Docbook.hs+4−3
@@ -126,9 +126,10 @@ writeDocbook opts (Pandoc meta blocks) = do
126126 defField "mathml" (case writerHTMLMathMethod opts of
127127 MathML -> True
128128 _ -> False) metadata
129− case writerTemplate opts of
130− Nothing -> return main
131− Just tpl -> renderTemplate' tpl context
129+ (if writerPreferAscii opts then toEntities else id) <$>
130+ case writerTemplate opts of
131+ Nothing -> return main
132+ Just tpl -> renderTemplate' tpl context
132133
133134-- | Convert an Element to Docbook.
134135elementToDocbook :: PandocMonad m => WriterOptions -> Int -> Element -> DB m Doc
src/Text/Pandoc/Writers/HTML.hs+11−7
@@ -75,7 +75,7 @@ import Text.Pandoc.Templates
7575import Text.Pandoc.Walk
7676import Text.Pandoc.Writers.Math
7777import Text.Pandoc.Writers.Shared
78−import Text.Pandoc.XML (escapeStringForXML, fromEntities)
78+import Text.Pandoc.XML (escapeStringForXML, fromEntities, toEntities)
7979#if MIN_VERSION_blaze_markup(0,6,3)
8080#else
8181import Text.Blaze.Internal (preEscapedString, preEscapedText)
@@ -206,7 +206,8 @@ writeHtmlString' :: PandocMonad m
206206 => WriterState -> WriterOptions -> Pandoc -> m Text
207207writeHtmlString' st opts d = do
208208 (body, context) <- evalStateT (pandocToHtml opts d) st
209− case writerTemplate opts of
209+ (if writerPreferAscii opts then toEntities else id) <$>
210+ case writerTemplate opts of
210211 Nothing -> return $ renderHtml' body
211212 Just tpl -> do
212213 -- warn if empty lang
@@ -221,16 +222,19 @@ writeHtmlString' st opts d = do
221222 lookup "sourcefile" (writerVariables opts)
222223 report $ NoTitleElement fallback
223224 return $ resetField "pagetitle" fallback context
224− renderTemplate' tpl $
225− defField "body" (renderHtml' body) context'
225+ renderTemplate' tpl
226+ (defField "body" (renderHtml' body) context')
226227
227228writeHtml' :: PandocMonad m => WriterState -> WriterOptions -> Pandoc -> m Html
228229writeHtml' st opts d =
229230 case writerTemplate opts of
230231 Just _ -> preEscapedText <$> writeHtmlString' st opts d
231− Nothing -> do
232− (body, _) <- evalStateT (pandocToHtml opts d) st
233− return body
232+ Nothing
233+ | writerPreferAscii opts
234+ -> preEscapedText <$> writeHtmlString' st opts d
235+ | otherwise -> do
236+ (body, _) <- evalStateT (pandocToHtml opts d) st
237+ return body
234238
235239-- result is (title, authors, date, toc, body, new variables)
236240pandocToHtml :: PandocMonad m
src/Text/Pandoc/Writers/ICML.hs+2−1
@@ -149,7 +149,8 @@ writeICML opts (Pandoc meta blocks) = do
149149 $ defField "charStyles" (render' $ charStylesToDoc st)
150150 $ defField "parStyles" (render' $ parStylesToDoc st)
151151 $ defField "hyperlinks" (render' $ hyperlinksToDoc $ links st) metadata
152− case writerTemplate opts of
152+ (if writerPreferAscii opts then toEntities else id) <$>
153+ case writerTemplate opts of
153154 Nothing -> return main
154155 Just tpl -> renderTemplate' tpl context
155156
src/Text/Pandoc/Writers/JATS.hs+2−1
@@ -102,7 +102,8 @@ docToJATS opts (Pandoc meta blocks) = do
102102 $ defField "mathml" (case writerHTMLMathMethod opts of
103103 MathML -> True
104104 _ -> False) metadata
105− case writerTemplate opts of
105+ (if writerPreferAscii opts then toEntities else id) <$>
106+ case writerTemplate opts of
106107 Nothing -> return main
107108 Just tpl -> renderTemplate' tpl context
108109
src/Text/Pandoc/Writers/LaTeX.hs+105−39
@@ -42,8 +42,9 @@ import Data.Aeson (FromJSON, object, (.=))
4242import Data.Char (isAlphaNum, isAscii, isDigit, isLetter, isPunctuation, ord,
4343 toLower)
4444import Data.List (foldl', intercalate, intersperse, isInfixOf, nubBy,
45− stripPrefix, (\\))
45+ stripPrefix, (\\), uncons)
4646import Data.Maybe (catMaybes, fromMaybe, isJust, mapMaybe, isNothing)
47+import qualified Data.Map as M
4748import Data.Text (Text)
4849import qualified Data.Text as T
4950import Network.URI (unEscapeString)
@@ -63,6 +64,7 @@ import Text.Pandoc.Walk
6364import Text.Pandoc.Writers.Shared
6465import qualified Text.Parsec as P
6566import Text.Printf (printf)
67+import qualified Data.Text.Normalize as Normalize
6668
6769data WriterState =
6870 WriterState { stInNote :: Bool -- true if we're in a note
@@ -318,46 +320,110 @@ data StringContext = TextString
318320
319321-- escape things as needed for LaTeX
320322stringToLaTeX :: PandocMonad m => StringContext -> String -> LW m String
321−stringToLaTeX _ [] = return ""
322−stringToLaTeX ctx (x:xs) = do
323+stringToLaTeX context zs = do
323324 opts <- gets stOptions
324− rest <- stringToLaTeX ctx xs
325− let ligatures = isEnabled Ext_smart opts && ctx == TextString
326− let isUrl = ctx == URLString
327− return $
325+ go opts context $
326+ if writerPreferAscii opts
327+ then T.unpack $ Normalize.normalize Normalize.NFD $ T.pack zs
328+ else zs
329+ where
330+ go _ _ [] = return ""
331+ go opts ctx (x:xs) = do
332+ let ligatures = isEnabled Ext_smart opts && ctx == TextString
333+ let isUrl = ctx == URLString
334+ let mbAccentCmd =
335+ if writerPreferAscii opts && ctx == TextString
336+ then uncons xs >>= \(c,_) -> M.lookup c accents
337+ else Nothing
338+ let emits s =
339+ case mbAccentCmd of
340+ Just cmd -> ((cmd ++ "{" ++ s ++ "}") ++)
341+ <$> go opts ctx (drop 1 xs) -- drop combining accent
342+ Nothing -> (s++) <$> go opts ctx xs
343+ let emitc c =
344+ case mbAccentCmd of
345+ Just cmd -> ((cmd ++ "{" ++ [c] ++ "}") ++)
346+ <$> go opts ctx (drop 1 xs) -- drop combining accent
347+ Nothing -> (c:) <$> go opts ctx xs
328348 case x of
329− '{' -> "\\{" ++ rest
330− '}' -> "\\}" ++ rest
331− '`' | ctx == CodeString -> "\\textasciigrave{}" ++ rest
332− '$' | not isUrl -> "\\$" ++ rest
333− '%' -> "\\%" ++ rest
334− '&' -> "\\&" ++ rest
335− '_' | not isUrl -> "\\_" ++ rest
336− '#' -> "\\#" ++ rest
337− '-' | not isUrl -> case xs of
338− -- prevent adjacent hyphens from forming ligatures
339− ('-':_) -> "-\\/" ++ rest
340− _ -> '-' : rest
341− '~' | not isUrl -> "\\textasciitilde{}" ++ rest
342− '^' -> "\\^{}" ++ rest
343− '\\'| isUrl -> '/' : rest -- NB. / works as path sep even on Windows
344− | otherwise -> "\\textbackslash{}" ++ rest
345− '|' | not isUrl -> "\\textbar{}" ++ rest
346− '<' -> "\\textless{}" ++ rest
347− '>' -> "\\textgreater{}" ++ rest
348− '[' -> "{[}" ++ rest -- to avoid interpretation as
349− ']' -> "{]}" ++ rest -- optional arguments
350− '\'' | ctx == CodeString -> "\\textquotesingle{}" ++ rest
351− '\160' -> "~" ++ rest
352− '\x202F' -> "\\," ++ rest
353− '\x2026' -> "\\ldots{}" ++ rest
354− '\x2018' | ligatures -> "`" ++ rest
355− '\x2019' | ligatures -> "'" ++ rest
356− '\x201C' | ligatures -> "``" ++ rest
357− '\x201D' | ligatures -> "''" ++ rest
358− '\x2014' | ligatures -> "---" ++ rest
359− '\x2013' | ligatures -> "--" ++ rest
360− _ -> x : rest
349+ '{' -> emits "\\{"
350+ '}' -> emits "\\}"
351+ '`' | ctx == CodeString -> emits "\\textasciigrave{}"
352+ '$' | not isUrl -> emits "\\$"
353+ '%' -> emits "\\%"
354+ '&' -> emits "\\&"
355+ '_' | not isUrl -> emits "\\_"
356+ '#' -> emits "\\#"
357+ '-' | not isUrl -> case xs of
358+ -- prevent adjacent hyphens from forming ligatures
359+ ('-':_) -> emits "-\\/"
360+ _ -> emitc '-'
361+ '~' | not isUrl -> emits "\\textasciitilde{}"
362+ '^' -> emits "\\^{}"
363+ '\\'| isUrl -> emitc '/' -- NB. / works as path sep even on Windows
364+ | otherwise -> emits "\\textbackslash{}"
365+ '|' | not isUrl -> emits "\\textbar{}"
366+ '<' -> emits "\\textless{}"
367+ '>' -> emits "\\textgreater{}"
368+ '[' -> emits "{[}" -- to avoid interpretation as
369+ ']' -> emits "{]}" -- optional arguments
370+ '\'' | ctx == CodeString -> emits "\\textquotesingle{}"
371+ '\160' -> emits "~"
372+ '\x202F' -> emits "\\,"
373+ '\x2026' -> emits "\\ldots{}"
374+ '\x2018' | ligatures -> emits "`"
375+ '\x2019' | ligatures -> emits "'"
376+ '\x201C' | ligatures -> emits "``"
377+ '\x201D' | ligatures -> emits "''"
378+ '\x2014' | ligatures -> emits "---"
379+ '\x2013' | ligatures -> emits "--"
380+ _ | writerPreferAscii opts
381+ -> case x of
382+ 'ı' -> emits "\\i "
383+ 'ȷ' -> emits "\\j "
384+ 'å' -> emits "\\aa "
385+ 'Å' -> emits "\\AA "
386+ 'ß' -> emits "\\ss "
387+ 'ø' -> emits "\\o "
388+ 'Ø' -> emits "\\O "
389+ 'Ł' -> emits "\\L "
390+ 'ł' -> emits "\\l "
391+ 'æ' -> emits "\\ae "
392+ 'Æ' -> emits "\\AE "
393+ 'œ' -> emits "\\oe "
394+ 'Œ' -> emits "\\OE "
395+ '£' -> emits "\\pounds "
396+ '€' -> emits "\\euro "
397+ '©' -> emits "\\copyright "
398+ _ -> emitc x
399+ | otherwise -> emitc x
400+
401+accents :: M.Map Char String
402+accents = M.fromList
403+ [ ('\779' , "\\H")
404+ , ('\768' , "\\`")
405+ , ('\769' , "\\'")
406+ , ('\770' , "\\^")
407+ , ('\771' , "\\~")
408+ , ('\776' , "\\\"")
409+ , ('\775' , "\\.")
410+ , ('\772' , "\\=")
411+ , ('\781' , "\\|")
412+ , ('\817' , "\\b")
413+ , ('\807' , "\\c")
414+ , ('\783' , "\\G")
415+ , ('\777' , "\\h")
416+ , ('\803' , "\\d")
417+ , ('\785' , "\\f")
418+ , ('\778' , "\\r")
419+ , ('\865' , "\\t")
420+ , ('\782' , "\\U")
421+ , ('\780' , "\\v")
422+ , ('\774' , "\\u")
423+ , ('\808' , "\\k")
424+ , ('\785' , "\\newtie")
425+ , ('\8413', "\\textcircled")
426+ ]
361427
362428toLabel :: PandocMonad m => String -> LW m String
363429toLabel z = go `fmap` stringToLaTeX URLString z
src/Text/Pandoc/Writers/Man.hs+3−1
@@ -45,6 +45,7 @@ import Text.Pandoc.Logging
4545import Text.Pandoc.Options
4646import Text.Pandoc.Pretty
4747import Text.Pandoc.Shared
48+import Text.Pandoc.Groff (groffEscape)
4849import Text.Pandoc.Templates
4950import Text.Pandoc.Writers.Math
5051import Text.Pandoc.Writers.Shared
@@ -107,7 +108,8 @@ pandocToMan opts (Pandoc meta blocks) = do
107108 $ defField "has-tables" hasTables
108109 $ defField "hyphenate" True
109110 $ defField "pandoc-version" pandocVersion metadata
110− case writerTemplate opts of
111+ (if writerPreferAscii opts then groffEscape else id) <$>
112+ case writerTemplate opts of
111113 Nothing -> return main
112114 Just tpl -> renderTemplate' tpl context
113115
src/Text/Pandoc/Writers/Ms.hs+3−1
@@ -60,6 +60,7 @@ import Text.Pandoc.Shared
6060import Text.Pandoc.Templates
6161import Text.Pandoc.Writers.Math
6262import Text.Pandoc.Writers.Shared
63+import Text.Pandoc.Groff (groffEscape)
6364import Text.Printf (printf)
6465import Text.TeXMath (writeEqn)
6566
@@ -127,7 +128,8 @@ pandocToMs opts (Pandoc meta blocks) = do
127128 $ defField "title-meta" titleMeta
128129 $ defField "author-meta" (intercalate "; " authorsMeta)
129130 $ defField "highlighting-macros" highlightingMacros metadata
130− case writerTemplate opts of
131+ (if writerPreferAscii opts then groffEscape else id) <$>
132+ case writerTemplate opts of
131133 Nothing -> return main
132134 Just tpl -> renderTemplate' tpl context
133135
src/Text/Pandoc/Writers/OPML.hs+2−1
@@ -62,7 +62,8 @@ writeOPML opts (Pandoc meta blocks) = do
6262 meta'
6363 main <- (render colwidth . vcat) <$> mapM (elementToOPML opts) elements
6464 let context = defField "body" main metadata
65− case writerTemplate opts of
65+ (if writerPreferAscii opts then toEntities else id) <$>
66+ case writerTemplate opts of
6667 Nothing -> return main
6768 Just tpl -> renderTemplate' tpl context
6869
test/command/ascii.mdnew file+45
@@ -0,0 +1,45 @@
1+```
2+pandoc -t html --ascii
3+äéıå
4+^D
5+<p>&#228;&#233;&#305;&#229;</p>
6+```
7+
8+```
9+pandoc -t latex --ascii
10+äéıå
11+^D
12+\"{a}\'{e}\i \r{a}
13+```
14+
15+```
16+pandoc -t man --ascii
17+äéıå
18+^D
19+.PP
20+\[u00E4]\[u00E9]\[u0131]\[u00E5]
21+```
22+
23+```
24+pandoc -t ms --ascii
25+äéıå
26+^D
27+.LP
28+\[u00E4]\[u00E9]\[u0131]\[u00E5]
29+```
30+
31+```
32+pandoc -t docbook --ascii
33+äéıå
34+^D
35+<para>
36+ &#228;&#233;&#305;&#229;
37+</para>
38+```
39+
40+```
41+pandoc -t jats --ascii
42+äéıå
43+^D
44+<p>&#228;&#233;&#305;&#229;</p>
45+```