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