1{-# LANGUAGE OverloadedStrings #-}
2{- |
3   Module      : Text.Pandoc.Writers.AsciiDoc
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
11Conversion of 'Pandoc' documents to asciidoc.
12
13Note that some information may be lost in conversion, due to
14expressive limitations of asciidoc.  Footnotes and table cells with
15paragraphs (or other block items) are not possible in asciidoc.
16If pandoc encounters one of these, it will insert a message indicating
17that it has omitted the construct.
18
19AsciiDoc:  <http://www.methods.co.nz/asciidoc/>
20-}
21module Text.Pandoc.Writers.AsciiDoc (writeAsciiDoc, writeAsciiDoctor) where
22import Control.Monad.State.Strict
23import Data.Char (isPunctuation, isSpace)
24import Data.List (intercalate, intersperse)
25import Data.List.NonEmpty (NonEmpty(..))
26import Data.Maybe (fromMaybe, isJust)
27import qualified Data.Set as Set
28import qualified Data.Text as T
29import Data.Text (Text)
30import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
31import Text.Pandoc.Definition
32import Text.Pandoc.ImageSize
33import Text.Pandoc.Logging
34import Text.Pandoc.Options
35import Text.Pandoc.Parsing hiding (blankline, space)
36import Text.DocLayout
37import Text.Pandoc.Shared
38import Text.Pandoc.Templates (renderTemplate)
39import Text.Pandoc.Writers.Shared
40
41
42data WriterState = WriterState { defListMarker       :: Text
43                               , orderedListLevel    :: Int
44                               , bulletListLevel     :: Int
45                               , intraword           :: Bool
46                               , autoIds             :: Set.Set Text
47                               , asciidoctorVariant  :: Bool
48                               , inList              :: Bool
49                               , hasMath             :: Bool
50                               -- |0 is no table
51                               -- 1 is top level table
52                               -- 2 is a table in a table
53                               , tableNestingLevel   :: Int
54                               }
55
56defaultWriterState :: WriterState
57defaultWriterState = WriterState { defListMarker      = "::"
58                                 , orderedListLevel   = 0
59                                 , bulletListLevel    = 0
60                                 , intraword          = False
61                                 , autoIds            = Set.empty
62                                 , asciidoctorVariant = False
63                                 , inList             = False
64                                 , hasMath            = False
65                                 , tableNestingLevel  = 0
66                                 }
67
68-- | Convert Pandoc to AsciiDoc.
69writeAsciiDoc :: PandocMonad m => WriterOptions -> Pandoc -> m Text
70writeAsciiDoc opts document =
71  evalStateT (pandocToAsciiDoc opts document) defaultWriterState
72
73-- | Convert Pandoc to AsciiDoctor compatible AsciiDoc.
74writeAsciiDoctor :: PandocMonad m => WriterOptions -> Pandoc -> m Text
75writeAsciiDoctor opts document =
76  evalStateT (pandocToAsciiDoc opts document)
77    defaultWriterState{ asciidoctorVariant = True }
78
79type ADW = StateT WriterState
80
81-- | Return asciidoc representation of document.
82pandocToAsciiDoc :: PandocMonad m => WriterOptions -> Pandoc -> ADW m Text
83pandocToAsciiDoc opts (Pandoc meta blocks) = do
84  let titleblock = not $ null (docTitle meta) && null (docAuthors meta) &&
85                         null (docDate meta)
86  let colwidth = if writerWrapText opts == WrapAuto
87                    then Just $ writerColumns opts
88                    else Nothing
89  metadata <- metaToContext opts
90              (blockListToAsciiDoc opts)
91              (fmap chomp . inlineListToAsciiDoc opts)
92              meta
93  main <- blockListToAsciiDoc opts $ makeSections False (Just 1) blocks
94  st <- get
95  let context  = defField "body" main
96               $ defField "toc"
97                  (writerTableOfContents opts &&
98                   isJust (writerTemplate opts))
99               $ defField "math" (hasMath st)
100               $ defField "titleblock" titleblock metadata
101  return $ render colwidth $
102    case writerTemplate opts of
103       Nothing  -> main
104       Just tpl -> renderTemplate tpl context
105
106-- | Escape special characters for AsciiDoc.
107escapeString :: Text -> Text
108escapeString t
109  | T.any (== '{') t = T.concatMap escChar t
110  | otherwise        = t
111  where escChar '{' = "\\{"
112        escChar c   = T.singleton c
113
114-- | Ordered list start parser for use in Para below.
115olMarker :: Parser Text ParserState Char
116olMarker = do (start, style', delim) <- anyOrderedListMarker
117              if delim == Period &&
118                          (style' == UpperAlpha || (style' == UpperRoman &&
119                          start `elem` [1, 5, 10, 50, 100, 500, 1000]))
120                          then spaceChar >> spaceChar
121                          else spaceChar
122
123-- | True if string begins with an ordered list marker
124-- or would be interpreted as an AsciiDoc option command
125needsEscaping :: Text -> Bool
126needsEscaping s = beginsWithOrderedListMarker s || isBracketed s
127  where
128    beginsWithOrderedListMarker str =
129      case runParser olMarker defaultParserState "para start" (T.take 10 str) of
130             Left  _ -> False
131             Right _ -> True
132    isBracketed t
133      | Just ('[', t') <- T.uncons t
134      , Just (_, ']')  <- T.unsnoc t'
135      = True
136      | otherwise = False
137
138-- | Convert Pandoc block element to asciidoc.
139blockToAsciiDoc :: PandocMonad m
140                => WriterOptions -- ^ Options
141                -> Block         -- ^ Block element
142                -> ADW m (Doc Text)
143blockToAsciiDoc _ Null = return empty
144blockToAsciiDoc opts (Div (id',"section":_,_)
145                       (Header level (_,cls,kvs) ils : xs)) = do
146  hdr <- blockToAsciiDoc opts (Header level (id',cls,kvs) ils)
147  rest <- blockListToAsciiDoc opts xs
148  return $ hdr $$ rest
149blockToAsciiDoc opts (Plain inlines) = do
150  contents <- inlineListToAsciiDoc opts inlines
151  return $ contents <> blankline
152blockToAsciiDoc opts (Para [Image attr alternate (src,tgt)])
153  -- image::images/logo.png[Company logo, title="blah"]
154  | Just tit <- T.stripPrefix "fig:" tgt
155  = (\args -> "image::" <> args <> blankline) <$>
156    imageArguments opts attr alternate src tit
157blockToAsciiDoc opts (Para inlines) = do
158  contents <- inlineListToAsciiDoc opts inlines
159  -- escape if para starts with ordered list marker
160  let esc = if needsEscaping (render Nothing contents)
161               then text "{empty}"
162               else empty
163  return $ esc <> contents <> blankline
164blockToAsciiDoc opts (LineBlock lns) = do
165  let docify line = if null line
166                    then return blankline
167                    else inlineListToAsciiDoc opts line
168  let joinWithLinefeeds = nowrap . mconcat . intersperse cr
169  contents <- joinWithLinefeeds <$> mapM docify lns
170  return $ "[verse]" $$ text "--" $$ contents $$ text "--" $$ blankline
171blockToAsciiDoc _ b@(RawBlock f s)
172  | f == "asciidoc" = return $ literal s
173  | otherwise         = do
174      report $ BlockNotRendered b
175      return empty
176blockToAsciiDoc _ HorizontalRule =
177  return $ blankline <> text "'''''" <> blankline
178blockToAsciiDoc opts (Header level (ident,_,_) inlines) = do
179  contents <- inlineListToAsciiDoc opts inlines
180  ids <- gets autoIds
181  let autoId = uniqueIdent (writerExtensions opts) inlines ids
182  modify $ \st -> st{ autoIds = Set.insert autoId ids }
183  let identifier = if T.null ident ||
184                      (isEnabled Ext_auto_identifiers opts && ident == autoId)
185                      then empty
186                      else "[[" <> literal ident <> "]]"
187  return $ identifier $$
188           nowrap (text (replicate (level + 1) '=') <> space <> contents) <>
189           blankline
190
191blockToAsciiDoc _ (CodeBlock (_,classes,_) str) = return $ flush (
192  if null classes
193     then "...." $$ literal str $$ "...."
194     else attrs $$ "----" $$ literal str $$ "----")
195  <> blankline
196    where attrs = "[" <> literal (T.intercalate "," ("source" : classes)) <> "]"
197blockToAsciiDoc opts (BlockQuote blocks) = do
198  contents <- blockListToAsciiDoc opts blocks
199  let isBlock (BlockQuote _) = True
200      isBlock _              = False
201  -- if there are nested block quotes, put in an open block
202  let contents' = if any isBlock blocks
203                     then "--" $$ contents $$ "--"
204                     else contents
205  let bar = text "____"
206  return $ bar $$ chomp contents' $$ bar <> blankline
207blockToAsciiDoc opts block@(Table _ blkCapt specs thead tbody tfoot) = do
208  let (caption, aligns, widths, headers, rows) =
209        toLegacyTable blkCapt specs thead tbody tfoot
210  caption' <- inlineListToAsciiDoc opts caption
211  let caption'' = if null caption
212                     then empty
213                     else "." <> caption' <> cr
214  let isSimple = all (== 0) widths
215  let relativePercentWidths = if isSimple
216                                 then widths
217                                 else map (/ sum widths) widths
218  let widths'' :: [Integer]
219      widths'' = map (floor . (* 100)) relativePercentWidths
220  -- ensure that the widths sum to 100
221  let widths' = case widths'' of
222                     _ | isSimple -> widths''
223                     (w:ws) | sum (w:ws) < 100
224                               -> (100 - sum ws) : ws
225                     ws        -> ws
226  let totalwidth :: Integer
227      totalwidth = floor $ sum widths * 100
228  let colspec al wi = (case al of
229                         AlignLeft    -> "<"
230                         AlignCenter  -> "^"
231                         AlignRight   -> ">"
232                         AlignDefault -> "") ++
233                      if wi == 0 then "" else show wi ++ "%"
234  let headerspec = if all null headers
235                      then empty
236                      else text "options=\"header\","
237  let widthspec = if totalwidth == 0
238                     then empty
239                     else text "width="
240                          <> doubleQuotes (text $ show totalwidth ++ "%")
241                          <> text ","
242  let tablespec = text "["
243         <> widthspec
244         <> text "cols="
245         <> doubleQuotes (text $ intercalate ","
246             $ zipWith colspec aligns widths')
247         <> text ","
248         <> headerspec <> text "]"
249
250  -- construct cells and recurse in case of nested tables
251  parentTableLevel <- gets tableNestingLevel
252  let currentNestingLevel = parentTableLevel + 1
253
254  modify $ \st -> st{ tableNestingLevel = currentNestingLevel }
255
256  let separator = text (if parentTableLevel == 0
257                          then "|"  -- top level separator
258                          else "!") -- nested separator
259
260  let makeCell [Plain x] = do d <- blockListToAsciiDoc opts [Plain x]
261                              return $ separator <> chomp d
262      makeCell [Para x]  = makeCell [Plain x]
263      makeCell []        = return separator
264      makeCell bs        = if currentNestingLevel == 2
265                             then do
266                               --asciidoc only supports nesting once
267                               report $ BlockNotRendered block
268                               return separator
269                             else do
270                               d <- blockListToAsciiDoc opts bs
271                               return $ (text "a" <> separator) $$ d
272
273  let makeRow cells = hsep `fmap` mapM makeCell cells
274  rows' <- mapM makeRow rows
275  head' <- makeRow headers
276  modify $ \st -> st{ tableNestingLevel = parentTableLevel }
277  let head'' = if all null headers then empty else head'
278  let colwidth = if writerWrapText opts == WrapAuto
279                    then writerColumns opts
280                    else 100000
281  let maxwidth = maximum $ fmap offset (head' :| rows')
282  let body = if maxwidth > colwidth then vsep rows' else vcat rows'
283  let border = separator <> text "==="
284  return $
285    caption'' $$ tablespec $$ border $$ head'' $$ body $$ border $$ blankline
286blockToAsciiDoc opts (BulletList items) = do
287  inlist <- gets inList
288  modify $ \st -> st{ inList = True }
289  contents <- mapM (bulletListItemToAsciiDoc opts) items
290  modify $ \st -> st{ inList = inlist }
291  return $ mconcat contents <> blankline
292blockToAsciiDoc opts (OrderedList (start, sty, _delim) items) = do
293  let listStyle = case sty of
294                       DefaultStyle -> []
295                       Decimal      -> ["arabic"]
296                       Example      -> []
297                       _            -> [T.toLower (tshow sty)]
298  let listStart = ["start=" <> tshow start | start /= 1]
299  let listoptions = case T.intercalate ", " (listStyle ++ listStart) of
300                          "" -> empty
301                          x  -> brackets (literal x)
302  inlist <- gets inList
303  modify $ \st -> st{ inList = True }
304  contents <- mapM (orderedListItemToAsciiDoc opts) items
305  modify $ \st -> st{ inList = inlist }
306  return $ listoptions $$ mconcat contents <> blankline
307blockToAsciiDoc opts (DefinitionList items) = do
308  inlist <- gets inList
309  modify $ \st -> st{ inList = True }
310  contents <- mapM (definitionListItemToAsciiDoc opts) items
311  modify $ \st -> st{ inList = inlist }
312  return $ mconcat contents <> blankline
313blockToAsciiDoc opts (Div (ident,classes,_) bs) = do
314  let identifier = if T.null ident then empty else "[[" <> literal ident <> "]]"
315  let admonitions = ["attention","caution","danger","error","hint",
316                     "important","note","tip","warning"]
317  contents <-
318       case classes of
319         (l:_) | l `elem` admonitions -> do
320             let (titleBs, bodyBs) =
321                     case bs of
322                       (Div (_,["title"],_) ts : rest) -> (ts, rest)
323                       _ -> ([], bs)
324             admonitionTitle <- if null titleBs
325                                   then return mempty
326                                   else ("." <>) <$>
327                                         blockListToAsciiDoc opts titleBs
328             admonitionBody <- blockListToAsciiDoc opts bodyBs
329             return $ "[" <> literal (T.toUpper l) <> "]" $$
330                      chomp admonitionTitle $$
331                      "====" $$
332                      chomp admonitionBody $$
333                      "===="
334         _ -> blockListToAsciiDoc opts bs
335  return $ identifier $$ contents $$ blankline
336
337-- | Convert bullet list item (list of blocks) to asciidoc.
338bulletListItemToAsciiDoc :: PandocMonad m
339                         => WriterOptions -> [Block] -> ADW m (Doc Text)
340bulletListItemToAsciiDoc opts blocks = do
341  lev <- gets bulletListLevel
342  modify $ \s -> s{ bulletListLevel = lev + 1 }
343  contents <- foldM (addBlock opts) empty blocks
344  modify $ \s -> s{ bulletListLevel = lev }
345  let marker = text (replicate (lev + 1) '*')
346  return $ marker <> text " " <> listBegin blocks <>
347    contents <> cr
348
349addBlock :: PandocMonad m
350         => WriterOptions -> Doc Text -> Block -> ADW m (Doc Text)
351addBlock opts d b = do
352  x <- chomp <$> blockToAsciiDoc opts b
353  return $
354    case b of
355        BulletList{} -> d <> cr <> x
356        OrderedList{} -> d <> cr <> x
357        Para (Math DisplayMath _:_) -> d <> cr <> x
358        Plain (Math DisplayMath _:_) -> d <> cr <> x
359        Para{} | isEmpty d -> x
360        Plain{} | isEmpty d -> x
361        _ -> d <> cr <> text "+" <> cr <> x
362
363listBegin :: [Block] -> Doc Text
364listBegin blocks =
365        case blocks of
366          Para (Math DisplayMath _:_) : _  -> "{blank}"
367          Plain (Math DisplayMath _:_) : _ -> "{blank}"
368          Para _ : _                       -> empty
369          Plain _ : _                      -> empty
370          _ : _                            -> "{blank}"
371          []                               -> "{blank}"
372
373-- | Convert ordered list item (a list of blocks) to asciidoc.
374orderedListItemToAsciiDoc :: PandocMonad m
375                          => WriterOptions -- ^ options
376                          -> [Block]       -- ^ list item (list of blocks)
377                          -> ADW m (Doc Text)
378orderedListItemToAsciiDoc opts blocks = do
379  lev <- gets orderedListLevel
380  modify $ \s -> s{ orderedListLevel = lev + 1 }
381  contents <- foldM (addBlock opts) empty blocks
382  modify $ \s -> s{ orderedListLevel = lev }
383  let marker = text (replicate (lev + 1) '.')
384  return $ marker <> text " " <> listBegin blocks <> contents <> cr
385
386-- | Convert definition list item (label, list of blocks) to asciidoc.
387definitionListItemToAsciiDoc :: PandocMonad m
388                             => WriterOptions
389                             -> ([Inline],[[Block]])
390                             -> ADW m (Doc Text)
391definitionListItemToAsciiDoc opts (label, defs) = do
392  labelText <- inlineListToAsciiDoc opts label
393  marker <- gets defListMarker
394  if marker == "::"
395     then modify (\st -> st{ defListMarker = ";;"})
396     else modify (\st -> st{ defListMarker = "::"})
397  let divider = cr <> text "+" <> cr
398  let defsToAsciiDoc :: PandocMonad m => [Block] -> ADW m (Doc Text)
399      defsToAsciiDoc ds = (vcat . intersperse divider . map chomp)
400           `fmap` mapM (blockToAsciiDoc opts) ds
401  defs' <- mapM defsToAsciiDoc defs
402  modify (\st -> st{ defListMarker = marker })
403  let contents = nest 2 $ vcat $ intersperse divider $ map chomp defs'
404  return $ labelText <> literal marker <> cr <> contents <> cr
405
406-- | Convert list of Pandoc block elements to asciidoc.
407blockListToAsciiDoc :: PandocMonad m
408                    => WriterOptions -- ^ Options
409                    -> [Block]       -- ^ List of block elements
410                    -> ADW m (Doc Text)
411blockListToAsciiDoc opts blocks =
412  mconcat `fmap` mapM (blockToAsciiDoc opts) blocks
413
414data SpacyLocation = End | Start
415
416-- | Convert list of Pandoc inline elements to asciidoc.
417inlineListToAsciiDoc :: PandocMonad m =>
418                        WriterOptions ->
419                        [Inline] ->
420                        ADW m (Doc Text)
421inlineListToAsciiDoc opts lst = do
422  oldIntraword <- gets intraword
423  setIntraword False
424  result <- go lst
425  setIntraword oldIntraword
426  return result
427 where go [] = return empty
428       go (y:x:xs)
429         | not (isSpacy End y) = do
430           y' <- if isSpacy Start x
431                    then inlineToAsciiDoc opts y
432                    else withIntraword $ inlineToAsciiDoc opts y
433           x' <- withIntraword $ inlineToAsciiDoc opts x
434           xs' <- go xs
435           return (y' <> x' <> xs')
436         | not (isSpacy Start x) = do
437           y' <- withIntraword $ inlineToAsciiDoc opts y
438           xs' <- go (x:xs)
439           return (y' <> xs')
440       go (x:xs) = do
441           x' <- inlineToAsciiDoc opts x
442           xs' <- go xs
443           return (x' <> xs')
444       isSpacy :: SpacyLocation -> Inline -> Bool
445       isSpacy _ Space = True
446       isSpacy _ LineBreak = True
447       isSpacy _ SoftBreak = True
448       -- Note that \W characters count as spacy in AsciiDoc
449       -- for purposes of determining interword:
450       isSpacy End (Str xs) = case T.unsnoc xs of
451                                   Just (_, c) -> isPunctuation c || isSpace c
452                                   _           -> False
453       isSpacy Start (Str xs)
454         | Just (c, _) <- T.uncons xs = isPunctuation c || isSpace c
455       isSpacy _ _ = False
456
457setIntraword :: PandocMonad m => Bool -> ADW m ()
458setIntraword b = modify $ \st -> st{ intraword = b }
459
460withIntraword :: PandocMonad m => ADW m a -> ADW m a
461withIntraword p = setIntraword True *> p <* setIntraword False
462
463-- | Convert Pandoc inline element to asciidoc.
464inlineToAsciiDoc :: PandocMonad m => WriterOptions -> Inline -> ADW m (Doc Text)
465inlineToAsciiDoc opts (Emph [Strong xs]) =
466  inlineToAsciiDoc opts (Strong [Emph xs])  -- see #5565
467inlineToAsciiDoc opts (Emph lst) = do
468  contents <- inlineListToAsciiDoc opts lst
469  isIntraword <- gets intraword
470  let marker = if isIntraword then "__" else "_"
471  return $ marker <> contents <> marker
472inlineToAsciiDoc opts (Underline lst) = do
473  contents <- inlineListToAsciiDoc opts lst
474  return $ "+++" <> contents <> "+++"
475inlineToAsciiDoc opts (Strong lst) = do
476  contents <- inlineListToAsciiDoc opts lst
477  isIntraword <- gets intraword
478  let marker = if isIntraword then "**" else "*"
479  return $ marker <> contents <> marker
480inlineToAsciiDoc opts (Strikeout lst) = do
481  contents <- inlineListToAsciiDoc opts lst
482  return $ "[line-through]*" <> contents <> "*"
483inlineToAsciiDoc opts (Superscript lst) = do
484  contents <- inlineListToAsciiDoc opts lst
485  return $ "^" <> contents <> "^"
486inlineToAsciiDoc opts (Subscript lst) = do
487  contents <- inlineListToAsciiDoc opts lst
488  return $ "~" <> contents <> "~"
489inlineToAsciiDoc opts (SmallCaps lst) = inlineListToAsciiDoc opts lst
490inlineToAsciiDoc opts (Quoted qt lst) = do
491  isAsciidoctor <- gets asciidoctorVariant
492  inlineListToAsciiDoc opts $
493    case qt of
494      SingleQuote
495        | isAsciidoctor -> [Str "'`"] ++ lst ++ [Str "`'"]
496        | otherwise     -> [Str "`"] ++ lst ++ [Str "'"]
497      DoubleQuote
498        | isAsciidoctor -> [Str "\"`"] ++ lst ++ [Str "`\""]
499        | otherwise     -> [Str "``"] ++ lst ++ [Str "''"]
500inlineToAsciiDoc _ (Code _ str) = do
501  isAsciidoctor <- gets asciidoctorVariant
502  let escChar '`' = "\\'"
503      escChar c   = T.singleton c
504  let contents = literal (T.concatMap escChar str)
505  return $
506    if isAsciidoctor
507       then text "`+" <> contents <> "+`"
508       else text "`"  <> contents <> "`"
509inlineToAsciiDoc _ (Str str) = return $ literal $ escapeString str
510inlineToAsciiDoc _ (Math InlineMath str) = do
511  isAsciidoctor <- gets asciidoctorVariant
512  modify $ \st -> st{ hasMath = True }
513  let content = if isAsciidoctor
514                then literal str
515                else "$" <> literal str <> "$"
516  return $ "latexmath:[" <> content <> "]"
517inlineToAsciiDoc _ (Math DisplayMath str) = do
518  isAsciidoctor <- gets asciidoctorVariant
519  modify $ \st -> st{ hasMath = True }
520  let content = if isAsciidoctor
521                then literal str
522                else "\\[" <> literal str <> "\\]"
523  inlist <- gets inList
524  let sepline = if inlist
525                   then text "+"
526                   else blankline
527  return $
528      (cr <> sepline) $$ "[latexmath]" $$ "++++" $$
529      content $$ "++++" <> cr
530inlineToAsciiDoc _ il@(RawInline f s)
531  | f == "asciidoc" = return $ literal s
532  | otherwise         = do
533      report $ InlineNotRendered il
534      return empty
535inlineToAsciiDoc _ LineBreak = return $ " +" <> cr
536inlineToAsciiDoc _ Space = return space
537inlineToAsciiDoc opts SoftBreak =
538  case writerWrapText opts of
539       WrapAuto     -> return space
540       WrapPreserve -> return cr
541       WrapNone     -> return space
542inlineToAsciiDoc opts (Cite _ lst) = inlineListToAsciiDoc opts lst
543inlineToAsciiDoc opts (Link _ txt (src, _tit)) = do
544-- relative:  link:downloads/foo.zip[download foo.zip]
545-- abs:  http://google.cod[Google]
546-- or my@email.com[email john]
547  linktext <- inlineListToAsciiDoc opts txt
548  let isRelative = T.all (/= ':') src
549  let prefix = if isRelative
550                  then text "link:"
551                  else empty
552  let srcSuffix = fromMaybe src (T.stripPrefix "mailto:" src)
553  let useAuto = case txt of
554                      [Str s] | escapeURI s == srcSuffix -> True
555                      _       -> False
556  return $ if useAuto
557              then literal srcSuffix
558              else prefix <> literal src <> "[" <> linktext <> "]"
559inlineToAsciiDoc opts (Image attr alternate (src, tit)) =
560  ("image:" <>) <$> imageArguments opts attr alternate src tit
561inlineToAsciiDoc opts (Note [Para inlines]) =
562  inlineToAsciiDoc opts (Note [Plain inlines])
563inlineToAsciiDoc opts (Note [Plain inlines]) = do
564  contents  <- inlineListToAsciiDoc opts inlines
565  return $ text "footnote:[" <> contents <> "]"
566-- asciidoc can't handle blank lines in notes
567inlineToAsciiDoc _ (Note _) = return "[multiblock footnote omitted]"
568inlineToAsciiDoc opts (Span (ident,classes,_) ils) = do
569  contents <- inlineListToAsciiDoc opts ils
570  isIntraword <- gets intraword
571  let marker = if isIntraword then "##" else "#"
572  if T.null ident && null classes
573     then return contents
574     else do
575       let modifier = brackets $ literal $ T.unwords $
576            [ "#" <> ident | not (T.null ident)] ++ map ("." <>) classes
577       return $ modifier <> marker <> contents <> marker
578
579-- | Provides the arguments for both `image:` and `image::`
580-- e.g.: sunset.jpg[Sunset,300,200]
581imageArguments :: PandocMonad m => WriterOptions ->
582  Attr -> [Inline] -> Text -> Text ->
583  ADW m (Doc Text)
584imageArguments opts attr altText src title = do
585  let txt = if null altText || (altText == [Str ""])
586               then [Str "image"]
587               else altText
588  linktext <- inlineListToAsciiDoc opts txt
589  let linktitle = if T.null title
590                     then empty
591                     else ",title=\"" <> literal title <> "\""
592      showDim dir = case dimension dir attr of
593                      Just (Percent a) ->
594                        ["scaledwidth=" <> text (show (Percent a))]
595                      Just dim         ->
596                        [text (show dir) <> "=" <>
597                          literal (showInPixel opts dim)]
598                      Nothing          ->
599                        []
600      dimList = showDim Width ++ showDim Height
601      dims = if null dimList
602                then empty
603                else "," <> mconcat (intersperse "," dimList)
604  return $ literal src <> "[" <> linktext <> linktitle <> dims <> "]"
605