git.iko.soy/typeable/pandoc
on master help sign in
Initial addition of groff ms writer.

* New module: Text.Pandoc.Writers.Ms. * New template: default.ms. * The writer uses texmath's new eqn writer to convert math to eqn format, so a ms file produced with this writer should be processed with `groff -ms -e` if it contains math.

commit6c204ea2bd9b9526af3b60485d3787da6b7bf8ac authorJohn MacFarlane <jgm@berkeley.edu> dateWed 15 Mar 2017, 21:04 (10 years ago) parentf4ac0ed
9 files changed+1690−1
data/templates/default.ms+31 pandoc.cabal+5−1 src/Text/Pandoc.hs+3 src/Text/Pandoc/App.hs+1 src/Text/Pandoc/Data.hsb+1 src/Text/Pandoc/Writers/Ms.hs+426 test/Tests/Old.hs+3 test/tables.ms+280 test/writer.ms+940
data/templates/default.msnew file+31
@@ -0,0 +1,31 @@
1+$if(adjusting)$
2+.ad $adjusting$
3+$endif$
4+$if(hyphenate)$
5+.hy
6+$else$
7+.nh \" Turn off hyphenation by default.
8+$endif$
9+$if(has-inline-math)$
10+.EQ
11+delim ||
12+.EN
13+$endif$
14+$if(title)$
15+.TL
16+$title$
17+$endif$
18+$for(author)$
19+.AU
20+$author$
21+$endfor$
22+$for(header-includes)$
23+$header-includes$
24+$endfor$
25+$for(include-before)$
26+$include-before$
27+$endfor$
28+$body$
29+$for(include-after)$
30+$include-after$
31+$endfor$
pandoc.cabal+5−1
@@ -49,6 +49,7 @@ Data-Files:
4949 data/templates/default.context
5050 data/templates/default.texinfo
5151 data/templates/default.man
52+ data/templates/default.ms
5253 data/templates/default.markdown
5354 data/templates/default.muse
5455 data/templates/default.commonmark
@@ -161,6 +162,7 @@ Extra-Source-Files:
161162 test/tables.html5
162163 test/tables.latex
163164 test/tables.man
165+ test/tables.ms
164166 test/tables.plain
165167 test/tables.markdown
166168 test/tables.mediawiki
@@ -184,6 +186,7 @@ Extra-Source-Files:
184186 test/writer.html4
185187 test/writer.html5
186188 test/writer.man
189+ test/writer.ms
187190 test/writer.markdown
188191 test/writer.plain
189192 test/writer.mediawiki
@@ -280,7 +283,7 @@ Library
280283 safe >= 0.3 && < 0.4,
281284 zip-archive >= 0.2.3.4 && < 0.4,
282285 HTTP >= 4000.0.5 && < 4000.4,
283− texmath >= 0.9.3 && < 0.10,
286+ texmath >= 0.9.4 && < 0.10,
284287 xml >= 1.3.12 && < 1.4,
285288 random >= 1 && < 1.2,
286289 extensible-exceptions >= 0.1 && < 0.2,
@@ -382,6 +385,7 @@ Library
382385 Text.Pandoc.Writers.OpenDocument,
383386 Text.Pandoc.Writers.Texinfo,
384387 Text.Pandoc.Writers.Man,
388+ Text.Pandoc.Writers.Ms,
385389 Text.Pandoc.Writers.Markdown,
386390 Text.Pandoc.Writers.CommonMark,
387391 Text.Pandoc.Writers.Haddock,
src/Text/Pandoc.hs+3
@@ -120,6 +120,7 @@ module Text.Pandoc
120120 , writeOPML
121121 , writeOpenDocument
122122 , writeMan
123+ , writeMs
123124 , writeMediaWiki
124125 , writeDokuWiki
125126 , writeZimWiki
@@ -190,6 +191,7 @@ import Text.Pandoc.Writers.HTML
190191import Text.Pandoc.Writers.ICML
191192import Text.Pandoc.Writers.LaTeX
192193import Text.Pandoc.Writers.Man
194+import Text.Pandoc.Writers.Ms
193195import Text.Pandoc.Writers.Markdown
194196import Text.Pandoc.Writers.MediaWiki
195197import Text.Pandoc.Writers.Muse
@@ -292,6 +294,7 @@ writers = [
292294 ,("context" , StringWriter writeConTeXt)
293295 ,("texinfo" , StringWriter writeTexinfo)
294296 ,("man" , StringWriter writeMan)
297+ ,("ms" , StringWriter writeMs)
295298 ,("markdown" , StringWriter writeMarkdown)
296299 ,("markdown_strict" , StringWriter writeMarkdown)
297300 ,("markdown_phpextra" , StringWriter writeMarkdown)
src/Text/Pandoc/App.hs+1
@@ -691,6 +691,7 @@ defaultWriterName x =
691691 ".icml" -> "icml"
692692 ".tei.xml" -> "tei"
693693 ".tei" -> "tei"
694+ ".ms" -> "ms"
694695 ['.',y] | y `elem` ['1'..'9'] -> "man"
695696 _ -> "html"
696697
src/Text/Pandoc/Data.hsb+1
@@ -13,3 +13,4 @@ dataFiles = map (\(fp, contents) ->
1313
1414dataFiles' :: [(FilePath, B.ByteString)]
1515dataFiles' = ("MANUAL.txt", %blob "MANUAL.txt") : %blobs "data"
16+
src/Text/Pandoc/Writers/Ms.hsnew file+426
@@ -0,0 +1,426 @@
1+{-
2+Copyright (C) 2007-2015 John MacFarlane <jgm@berkeley.edu>
3+
4+This program is free software; you can redistribute it and/or modify
5+it under the terms of the GNU General Public License as published by
6+the Free Software Foundation; either version 2 of the License, or
7+(at your option) any later version.
8+
9+This program is distributed in the hope that it will be useful,
10+but WITHOUT ANY WARRANTY; without even the implied warranty of
11+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+GNU General Public License for more details.
13+
14+You should have received a copy of the GNU General Public License
15+along with this program; if not, write to the Free Software
16+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17+-}
18+
19+{- |
20+ Module : Text.Pandoc.Writers.Ms
21+ Copyright : Copyright (C) 2007-2015 John MacFarlane
22+ License : GNU GPL, version 2 or above
23+
24+ Maintainer : John MacFarlane <jgm@berkeley.edu>
25+ Stability : alpha
26+ Portability : portable
27+
28+Conversion of 'Pandoc' documents to groff man page format.
29+
30+TODO:
31+
32+[ ] warning for non-rendered raw content
33+[ ] is there a better way to do strikeout?
34+[ ] strong + em doesn't seem to work
35+[ ] super + subscript don't seem to work
36+[ ] options for hyperlink rendering (currently footnote)
37+[ ] avoid note-in-note (which we currently get easily with
38+ links in footnotes)
39+[ ] can we get prettier output using .B, etc. instead of
40+ the inline forms?
41+[ ] tight/loose list distinction
42+[ ] internal hyperlinks (this seems to be possible since
43+ they exist in the groff manual PDF version)
44+[ ] use a custom macro for defn lists so they're configurable
45+[ ] better handling of accented characters and other non-ascii
46+ characters (e.g. curly quotes) -- we shouldn't rely on a
47+ utf8 compatible groff
48+[ ] avoid blank line after footnote marker when footnote has a
49+ paragraph
50+[ ] add via groff option to PDF module
51+[ ] better handling of autolinks?
52+[ ] better handling of images, perhaps converting to eps when
53+ going to PDF?
54+[ ] better template, with configurable page number, table of contents,
55+ columns, etc.
56+-}
57+
58+module Text.Pandoc.Writers.Ms ( writeMs ) where
59+import Text.Pandoc.Definition
60+import Text.Pandoc.Templates
61+import Text.Pandoc.Shared
62+import Text.Pandoc.Writers.Shared
63+import Text.Pandoc.Options
64+import Text.Pandoc.Writers.Math
65+import Text.Printf ( printf )
66+import Data.List ( stripPrefix, intersperse, intercalate )
67+import Data.Maybe (fromMaybe)
68+import Text.Pandoc.Pretty
69+import Text.Pandoc.Builder (deleteMeta)
70+import Text.Pandoc.Class (PandocMonad)
71+import Control.Monad.State
72+import Data.Char ( isDigit )
73+import Text.TeXMath (writeEqn)
74+
75+data WriterState = WriterState { stHasInlineMath :: Bool
76+ , stNotes :: [Note] }
77+type Note = [Block]
78+
79+type MS = StateT WriterState
80+
81+-- | Convert Pandoc to Ms.
82+writeMs :: PandocMonad m => WriterOptions -> Pandoc -> m String
83+writeMs opts document =
84+ evalStateT (pandocToMs opts document) (WriterState False [])
85+
86+-- | Return groff man representation of document.
87+pandocToMs :: PandocMonad m => WriterOptions -> Pandoc -> MS m String
88+pandocToMs opts (Pandoc meta blocks) = do
89+ let colwidth = if writerWrapText opts == WrapAuto
90+ then Just $ writerColumns opts
91+ else Nothing
92+ let render' = render colwidth
93+ titleText <- inlineListToMs' opts $ docTitle meta
94+ let title' = render' titleText
95+ let setFieldsFromTitle =
96+ case break (== ' ') title' of
97+ (cmdName, rest) -> case reverse cmdName of
98+ (')':d:'(':xs) | isDigit d ->
99+ defField "title" (reverse xs) .
100+ defField "section" [d] .
101+ case splitBy (=='|') rest of
102+ (ft:hds) ->
103+ defField "footer" (trim ft) .
104+ defField "header"
105+ (trim $ concat hds)
106+ [] -> id
107+ _ -> defField "title" title'
108+ metadata <- metaToJSON opts
109+ (fmap (render colwidth) . blockListToMs opts)
110+ (fmap (render colwidth) . inlineListToMs' opts)
111+ $ deleteMeta "title" meta
112+ body <- blockListToMs opts blocks
113+ let main = render' body
114+ hasInlineMath <- gets stHasInlineMath
115+ let context = defField "body" main
116+ $ setFieldsFromTitle
117+ $ defField "has-inline-math" hasInlineMath
118+ $ defField "hyphenate" True
119+ $ defField "pandoc-version" pandocVersion
120+ $ metadata
121+ case writerTemplate opts of
122+ Nothing -> return main
123+ Just tpl -> return $ renderTemplate' tpl context
124+
125+-- | Association list of characters to escape.
126+manEscapes :: [(Char, String)]
127+manEscapes = [ ('\160', "\\ ")
128+ , ('\'', "\\[aq]")
129+ , ('’', "'")
130+ , ('\x2014', "\\[em]")
131+ , ('\x2013', "\\[en]")
132+ , ('\x2026', "\\&...")
133+ , ('|', "\\[u007C]") -- because we use | for inline math
134+ ] ++ backslashEscapes "-@\\"
135+
136+-- | Escape | character, used to mark inline math, inside math.
137+escapeBar :: String -> String
138+escapeBar = concatMap go
139+ where go '|' = "\\[u007C]"
140+ go c = [c]
141+
142+-- | Escape special characters for Ms.
143+escapeString :: String -> String
144+escapeString = escapeStringUsing manEscapes
145+
146+-- | Escape a literal (code) section for Ms.
147+escapeCode :: String -> String
148+escapeCode = concat . intersperse "\n" . map escapeLine . lines where
149+ escapeLine codeline =
150+ case escapeStringUsing (manEscapes ++ backslashEscapes "\t ") codeline of
151+ a@('.':_) -> "\\&" ++ a
152+ b -> b
153+
154+-- We split inline lists into sentences, and print one sentence per
155+-- line. groff/troff treats the line-ending period differently.
156+-- See http://code.google.com/p/pandoc/issues/detail?id=148.
157+
158+-- | Returns the first sentence in a list of inlines, and the rest.
159+breakSentence :: [Inline] -> ([Inline], [Inline])
160+breakSentence [] = ([],[])
161+breakSentence xs =
162+ let isSentenceEndInline (Str ys@(_:_)) | last ys == '.' = True
163+ isSentenceEndInline (Str ys@(_:_)) | last ys == '?' = True
164+ isSentenceEndInline (LineBreak) = True
165+ isSentenceEndInline _ = False
166+ (as, bs) = break isSentenceEndInline xs
167+ in case bs of
168+ [] -> (as, [])
169+ [c] -> (as ++ [c], [])
170+ (c:Space:cs) -> (as ++ [c], cs)
171+ (c:SoftBreak:cs) -> (as ++ [c], cs)
172+ (Str ".":Str (')':ys):cs) -> (as ++ [Str ".", Str (')':ys)], cs)
173+ (x@(Str ('.':')':_)):cs) -> (as ++ [x], cs)
174+ (LineBreak:x@(Str ('.':_)):cs) -> (as ++[LineBreak], x:cs)
175+ (c:cs) -> (as ++ [c] ++ ds, es)
176+ where (ds, es) = breakSentence cs
177+
178+-- | Split a list of inlines into sentences.
179+splitSentences :: [Inline] -> [[Inline]]
180+splitSentences xs =
181+ let (sent, rest) = breakSentence xs
182+ in if null rest then [sent] else sent : splitSentences rest
183+
184+blockToMs :: PandocMonad m
185+ => WriterOptions -- ^ Options
186+ -> Block -- ^ Block element
187+ -> MS m Doc
188+blockToMs _ Null = return empty
189+blockToMs opts (Div _ bs) = blockListToMs opts bs
190+blockToMs opts (Plain inlines) =
191+ liftM vcat $ mapM (inlineListToMs' opts) $ splitSentences inlines
192+blockToMs opts (Para inlines) = do
193+ contents <- liftM vcat $ mapM (inlineListToMs' opts) $
194+ splitSentences inlines
195+ return $ text ".LP" $$ contents
196+blockToMs _ (RawBlock f str)
197+ | f == Format "man" = return $ text str
198+ | otherwise = return empty
199+blockToMs _ HorizontalRule = return $ text ".PP" $$ text " * * * * *"
200+blockToMs opts (Header level _ inlines) = do
201+ contents <- inlineListToMs' opts inlines
202+ let heading = if writerNumberSections opts
203+ then ".NH"
204+ else ".SH"
205+ return $ text heading <> space <> text (show level) $$ contents
206+blockToMs _ (CodeBlock _ str) = return $
207+ text ".IP" $$
208+ text ".nf" $$
209+ text "\\f[C]" $$
210+ text (escapeCode str) $$
211+ text "\\f[]" $$
212+ text ".fi"
213+blockToMs opts (LineBlock ls) = do
214+ blockToMs opts $ Para $ intercalate [LineBreak] ls
215+blockToMs opts (BlockQuote blocks) = do
216+ contents <- blockListToMs opts blocks
217+ return $ text ".RS" $$ contents $$ text ".RE"
218+blockToMs opts (Table caption alignments widths headers rows) =
219+ let aligncode AlignLeft = "l"
220+ aligncode AlignRight = "r"
221+ aligncode AlignCenter = "c"
222+ aligncode AlignDefault = "l"
223+ in do
224+ caption' <- inlineListToMs' opts caption
225+ let iwidths = if all (== 0) widths
226+ then repeat ""
227+ else map (printf "w(%0.1fn)" . (70 *)) widths
228+ -- 78n default width - 8n indent = 70n
229+ let coldescriptions = text $ intercalate " "
230+ (zipWith (\align width -> aligncode align ++ width)
231+ alignments iwidths) ++ "."
232+ colheadings <- mapM (blockListToMs opts) headers
233+ let makeRow cols = text "T{" $$
234+ (vcat $ intersperse (text "T}@T{") cols) $$
235+ text "T}"
236+ let colheadings' = if all null headers
237+ then empty
238+ else makeRow colheadings $$ char '_'
239+ body <- mapM (\row -> do
240+ cols <- mapM (blockListToMs opts) row
241+ return $ makeRow cols) rows
242+ return $ text ".PP" $$ caption' $$
243+ text ".TS" $$ text "tab(@);" $$ coldescriptions $$
244+ colheadings' $$ vcat body $$ text ".TE"
245+
246+blockToMs opts (BulletList items) = do
247+ contents <- mapM (bulletListItemToMs opts) items
248+ return (vcat contents)
249+blockToMs opts (OrderedList attribs items) = do
250+ let markers = take (length items) $ orderedListMarkers attribs
251+ let indent = 1 + (maximum $ map length markers)
252+ contents <- mapM (\(num, item) -> orderedListItemToMs opts num indent item) $
253+ zip markers items
254+ return (vcat contents)
255+blockToMs opts (DefinitionList items) = do
256+ contents <- mapM (definitionListItemToMs opts) items
257+ return (vcat contents)
258+
259+-- | Convert bullet list item (list of blocks) to man.
260+bulletListItemToMs :: PandocMonad m => WriterOptions -> [Block] -> MS m Doc
261+bulletListItemToMs _ [] = return empty
262+bulletListItemToMs opts ((Para first):rest) =
263+ bulletListItemToMs opts ((Plain first):rest)
264+bulletListItemToMs opts ((Plain first):rest) = do
265+ first' <- blockToMs opts (Plain first)
266+ rest' <- blockListToMs opts rest
267+ let first'' = text ".IP \\[bu] 2" $$ first'
268+ let rest'' = if null rest
269+ then empty
270+ else text ".RS 2" $$ rest' $$ text ".RE"
271+ return (first'' $$ rest'')
272+bulletListItemToMs opts (first:rest) = do
273+ first' <- blockToMs opts first
274+ rest' <- blockListToMs opts rest
275+ return $ text "\\[bu] .RS 2" $$ first' $$ rest' $$ text ".RE"
276+
277+-- | Convert ordered list item (a list of blocks) to man.
278+orderedListItemToMs :: PandocMonad m
279+ => WriterOptions -- ^ options
280+ -> String -- ^ order marker for list item
281+ -> Int -- ^ number of spaces to indent
282+ -> [Block] -- ^ list item (list of blocks)
283+ -> MS m Doc
284+orderedListItemToMs _ _ _ [] = return empty
285+orderedListItemToMs opts num indent ((Para first):rest) =
286+ orderedListItemToMs opts num indent ((Plain first):rest)
287+orderedListItemToMs opts num indent (first:rest) = do
288+ first' <- blockToMs opts first
289+ rest' <- blockListToMs opts rest
290+ let num' = printf ("%" ++ show (indent - 1) ++ "s") num
291+ let first'' = text (".IP \"" ++ num' ++ "\" " ++ show indent) $$ first'
292+ let rest'' = if null rest
293+ then empty
294+ else text ".RS 4" $$ rest' $$ text ".RE"
295+ return $ first'' $$ rest''
296+
297+-- | Convert definition list item (label, list of blocks) to man.
298+definitionListItemToMs :: PandocMonad m
299+ => WriterOptions
300+ -> ([Inline],[[Block]])
301+ -> MS m Doc
302+definitionListItemToMs opts (label, defs) = do
303+ labelText <- inlineListToMs' opts label
304+ contents <- if null defs
305+ then return empty
306+ else liftM vcat $ forM defs $ \blocks -> do
307+ let (first, rest) = case blocks of
308+ ((Para x):y) -> (Plain x,y)
309+ (x:y) -> (x,y)
310+ [] -> error "blocks is null"
311+ rest' <- liftM vcat $
312+ mapM (\item -> blockToMs opts item) rest
313+ first' <- blockToMs opts first
314+ return $ first' $$ text ".RS" $$ rest' $$ text ".RE"
315+ return $ text ".XP" $$ nowrap (text ".B \"" <> labelText <> text "\"")
316+ $$ text "\\~\\~" <> contents
317+
318+-- | Convert list of Pandoc block elements to man.
319+blockListToMs :: PandocMonad m
320+ => WriterOptions -- ^ Options
321+ -> [Block] -- ^ List of block elements
322+ -> MS m Doc
323+blockListToMs opts blocks =
324+ mapM (blockToMs opts) blocks >>= (return . vcat)
325+
326+-- | Convert list of Pandoc inline elements to ms.
327+inlineListToMs :: PandocMonad m => WriterOptions -> [Inline] -> MS m Doc
328+-- if list starts with ., insert a zero-width character \& so it
329+-- won't be interpreted as markup if it falls at the beginning of a line.
330+inlineListToMs opts lst@(Str ('.':_) : _) = mapM (inlineToMs opts) lst >>=
331+ (return . (text "\\&" <>) . hcat)
332+inlineListToMs opts lst = hcat <$> mapM (inlineToMs opts) lst
333+
334+-- This version to be used when there is no further inline content;
335+-- forces a note at the end.
336+inlineListToMs' :: PandocMonad m => WriterOptions -> [Inline] -> MS m Doc
337+inlineListToMs' opts lst = do
338+ x <- hcat <$> mapM (inlineToMs opts) lst
339+ y <- handleNotes opts empty
340+ return $ x <> y
341+
342+-- | Convert Pandoc inline element to man.
343+inlineToMs :: PandocMonad m => WriterOptions -> Inline -> MS m Doc
344+inlineToMs opts (Span _ ils) = inlineListToMs opts ils
345+inlineToMs opts (Emph lst) = do
346+ contents <- inlineListToMs opts lst
347+ return $ text "\\f[I]" <> contents <> text "\\f[]"
348+inlineToMs opts (Strong lst) = do
349+ contents <- inlineListToMs opts lst
350+ return $ text "\\f[B]" <> contents <> text "\\f[]"
351+inlineToMs opts (Strikeout lst) = do
352+ contents <- inlineListToMs opts lst
353+ return $ text "[STRIKEOUT:" <> contents <> char ']'
354+inlineToMs opts (Superscript lst) = do
355+ contents <- inlineListToMs opts lst
356+ return $ char '^' <> contents <> char '^'
357+inlineToMs opts (Subscript lst) = do
358+ contents <- inlineListToMs opts lst
359+ return $ char '~' <> contents <> char '~'
360+inlineToMs opts (SmallCaps lst) = inlineListToMs opts lst -- not supported
361+inlineToMs opts (Quoted SingleQuote lst) = do
362+ contents <- inlineListToMs opts lst
363+ return $ char '`' <> contents <> char '\''
364+inlineToMs opts (Quoted DoubleQuote lst) = do
365+ contents <- inlineListToMs opts lst
366+ return $ text "\\[lq]" <> contents <> text "\\[rq]"
367+inlineToMs opts (Cite _ lst) =
368+ inlineListToMs opts lst
369+inlineToMs _ (Code _ str) =
370+ return $ text $ "\\f[C]" ++ escapeCode str ++ "\\f[]"
371+inlineToMs _ (Str str) = return $ text $ escapeString str
372+inlineToMs opts (Math InlineMath str) = do
373+ modify $ \st -> st{ stHasInlineMath = True }
374+ res <- convertMath writeEqn InlineMath str
375+ case res of
376+ Left il -> inlineToMs opts il
377+ Right r -> return $ text "|" <> text (escapeBar r) <> text "|"
378+inlineToMs opts (Math DisplayMath str) = do
379+ res <- convertMath writeEqn InlineMath str
380+ case res of
381+ Left il -> do
382+ contents <- inlineToMs opts il
383+ return $ cr <> text ".RS" $$ contents $$ text ".RE"
384+ Right r -> return $
385+ cr <> text ".EQ" $$ text (escapeBar r) $$ text ".EN"
386+inlineToMs _ (RawInline f str)
387+ | f == Format "man" = return $ text str
388+ | otherwise = return empty
389+inlineToMs _ (LineBreak) = return $
390+ cr <> text ".PD 0" $$ text ".P" $$ text ".PD" <> cr
391+inlineToMs opts SoftBreak = handleNotes opts cr
392+inlineToMs opts Space = handleNotes opts space
393+inlineToMs opts (Link _ txt (src, _)) = do
394+ let srcSuffix = fromMaybe src (stripPrefix "mailto:" src)
395+ case txt of
396+ [Str s]
397+ | escapeURI s == srcSuffix ->
398+ return $ char '<' <> text srcSuffix <> char '>'
399+ _ -> do
400+ let linknote = [Plain [Str src]]
401+ inlineListToMs opts (txt ++ [Note linknote])
402+inlineToMs opts (Image attr alternate (source, tit)) = do
403+ let txt = if (null alternate) || (alternate == [Str ""]) ||
404+ (alternate == [Str source]) -- to prevent autolinks
405+ then [Str "image"]
406+ else alternate
407+ linkPart <- inlineToMs opts (Link attr txt (source, tit))
408+ return $ char '[' <> text "IMAGE: " <> linkPart <> char ']'
409+inlineToMs _ (Note contents) = do
410+ modify $ \st -> st{ stNotes = contents : stNotes st }
411+ return $ text "\\**"
412+
413+handleNotes :: PandocMonad m => WriterOptions -> Doc -> MS m Doc
414+handleNotes opts fallback = do
415+ notes <- gets stNotes
416+ if null notes
417+ then return fallback
418+ else do
419+ modify $ \st -> st{ stNotes = [] }
420+ vcat <$> mapM (handleNote opts) notes
421+
422+handleNote :: PandocMonad m => WriterOptions -> Note -> MS m Doc
423+handleNote opts bs = do
424+ contents <- blockListToMs opts bs
425+ return $ cr <> text ".FS" $$ contents $$ text ".FE" <> cr
426+
test/Tests/Old.hs+3
@@ -143,6 +143,9 @@ tests = [ testGroup "markdown"
143143 , testGroup "muse"
144144 [ testGroup "writer" $ writerTests "muse"
145145 ]
146+ , testGroup "ms"
147+ [ testGroup "writer" $ writerTests "ms"
148+ ]
146149 ]
147150
148151-- makes sure file is fully closed after reading
test/tables.msnew file+280
@@ -0,0 +1,280 @@
1+.LP
2+Simple table with caption:
3+.PP
4+Demonstration of simple table syntax.
5+.TS
6+tab(@);
7+r l c l.
8+T{
9+Right
10+T}@T{
11+Left
12+T}@T{
13+Center
14+T}@T{
15+Default
16+T}
17+_
18+T{
19+12
20+T}@T{
21+12
22+T}@T{
23+12
24+T}@T{
25+12
26+T}
27+T{
28+123
29+T}@T{
30+123
31+T}@T{
32+123
33+T}@T{
34+123
35+T}
36+T{
37+1
38+T}@T{
39+1
40+T}@T{
41+1
42+T}@T{
43+1
44+T}
45+.TE
46+.LP
47+Simple table without caption:
48+.PP
49+.TS
50+tab(@);
51+r l c l.
52+T{
53+Right
54+T}@T{
55+Left
56+T}@T{
57+Center
58+T}@T{
59+Default
60+T}
61+_
62+T{
63+12
64+T}@T{
65+12
66+T}@T{
67+12
68+T}@T{
69+12
70+T}
71+T{
72+123
73+T}@T{
74+123
75+T}@T{
76+123
77+T}@T{
78+123
79+T}
80+T{
81+1
82+T}@T{
83+1
84+T}@T{
85+1
86+T}@T{
87+1
88+T}
89+.TE
90+.LP
91+Simple table indented two spaces:
92+.PP
93+Demonstration of simple table syntax.
94+.TS
95+tab(@);
96+r l c l.
97+T{
98+Right
99+T}@T{
100+Left
101+T}@T{
102+Center
103+T}@T{
104+Default
105+T}
106+_
107+T{
108+12
109+T}@T{
110+12
111+T}@T{
112+12
113+T}@T{
114+12
115+T}
116+T{
117+123
118+T}@T{
119+123
120+T}@T{
121+123
122+T}@T{
123+123
124+T}
125+T{
126+1
127+T}@T{
128+1
129+T}@T{
130+1
131+T}@T{
132+1
133+T}
134+.TE
135+.LP
136+Multiline table with caption:
137+.PP
138+Here's the caption.
139+It may span multiple lines.
140+.TS
141+tab(@);
142+cw(10.5n) lw(9.6n) rw(11.4n) lw(23.6n).
143+T{
144+Centered
145+Header
146+T}@T{
147+Left
148+Aligned
149+T}@T{
150+Right
151+Aligned
152+T}@T{
153+Default aligned
154+T}
155+_
156+T{
157+First
158+T}@T{
159+row
160+T}@T{
161+12.0
162+T}@T{
163+Example of a row that spans
164+multiple lines.
165+T}
166+T{
167+Second
168+T}@T{
169+row
170+T}@T{
171+5.0
172+T}@T{
173+Here's another one.
174+Note
175+the blank line between rows.
176+T}
177+.TE
178+.LP
179+Multiline table without caption:
180+.PP
181+.TS
182+tab(@);
183+cw(10.5n) lw(9.6n) rw(11.4n) lw(23.6n).
184+T{
185+Centered
186+Header
187+T}@T{
188+Left
189+Aligned
190+T}@T{
191+Right
192+Aligned
193+T}@T{
194+Default aligned
195+T}
196+_
197+T{
198+First
199+T}@T{
200+row
201+T}@T{
202+12.0
203+T}@T{
204+Example of a row that spans
205+multiple lines.
206+T}
207+T{
208+Second
209+T}@T{
210+row
211+T}@T{
212+5.0
213+T}@T{
214+Here's another one.
215+Note
216+the blank line between rows.
217+T}
218+.TE
219+.LP
220+Table without column headers:
221+.PP
222+.TS
223+tab(@);
224+r l c r.
225+T{
226+12
227+T}@T{
228+12
229+T}@T{
230+12
231+T}@T{
232+12
233+T}
234+T{
235+123
236+T}@T{
237+123
238+T}@T{
239+123
240+T}@T{
241+123
242+T}
243+T{
244+1
245+T}@T{
246+1
247+T}@T{
248+1
249+T}@T{
250+1
251+T}
252+.TE
253+.LP
254+Multiline table without column headers:
255+.PP
256+.TS
257+tab(@);
258+cw(10.5n) lw(9.6n) rw(11.4n) lw(23.6n).
259+T{
260+First
261+T}@T{
262+row
263+T}@T{
264+12.0
265+T}@T{
266+Example of a row that spans
267+multiple lines.
268+T}
269+T{
270+Second
271+T}@T{
272+row
273+T}@T{
274+5.0
275+T}@T{
276+Here's another one.
277+Note
278+the blank line between rows.
279+T}
280+.TE
test/writer.msnew file+940
@@ -0,0 +1,940 @@
1+.hy
2+.EQ
3+delim ||
4+.EN
5+.TL
6+Pandoc Test Suite
7+.AU
8+John MacFarlane
9+.AU
10+Anonymous
11+.LP
12+This is a set of tests for pandoc.
13+Most of them are adapted from
14+John Gruber's markdown test suite.
15+.PP
16+ * * * * *
17+.SH 1
18+Headers
19+.SH 2
20+Level 2 with an embedded link\**
21+.FS
22+/url
23+.FE
24+.SH 3
25+Level 3 with \f[I]emphasis\f[]
26+.SH 4
27+Level 4
28+.SH 5
29+Level 5
30+.SH 1
31+Level 1
32+.SH 2
33+Level 2 with \f[I]emphasis\f[]
34+.SH 3
35+Level 3
36+.LP
37+with no blank line
38+.SH 2
39+Level 2
40+.LP
41+with no blank line
42+.PP
43+ * * * * *
44+.SH 1
45+Paragraphs
46+.LP
47+Here's a regular paragraph.
48+.LP
49+In Markdown 1.0.0 and earlier.
50+Version
51+8.
52+This line turns into a list item.
53+Because a hard\-wrapped line in the
54+middle of a paragraph looked like a
55+list item.
56+.LP
57+Here's one with a bullet.
58+* criminey.
59+.LP
60+There should be a hard line break
61+.PD 0
62+.P
63+.PD
64+here.
65+.PP
66+ * * * * *
67+.SH 1
68+Block Quotes
69+.LP
70+E\-mail style:
71+.RS
72+.LP
73+This is a block quote.
74+It is pretty short.
75+.RE
76+.RS
77+.LP
78+Code in a block quote:
79+.IP
80+.nf
81+\f[C]
82+sub\ status\ {
83+\ \ \ \ print\ "working";
84+}
85+\f[]
86+.fi
87+.LP
88+A list:
89+.IP "1." 3
90+item one
91+.IP "2." 3
92+item two
93+.LP
94+Nested block quotes:
95+.RS
96+.LP
97+nested
98+.RE
99+.RS
100+.LP
101+nested
102+.RE
103+.RE
104+.LP
105+This should not be a block quote: 2
106+> 1.
107+.LP
108+And a following paragraph.
109+.PP
110+ * * * * *
111+.SH 1
112+Code Blocks
113+.LP
114+Code:
115+.IP
116+.nf
117+\f[C]
118+\-\-\-\-\ (should\ be\ four\ hyphens)
119+
120+sub\ status\ {
121+\ \ \ \ print\ "working";
122+}
123+
124+this\ code\ block\ is\ indented\ by\ one\ tab
125+\f[]
126+.fi
127+.LP
128+And:
129+.IP
130+.nf
131+\f[C]
132+\ \ \ \ this\ code\ block\ is\ indented\ by\ two\ tabs
133+
134+These\ should\ not\ be\ escaped:\ \ \\$\ \\\\\ \\>\ \\[\ \\{
135+\f[]
136+.fi
137+.PP
138+ * * * * *
139+.SH 1
140+Lists
141+.SH 2
142+Unordered
143+.LP
144+Asterisks tight:
145+.IP \[bu] 2
146+asterisk 1
147+.IP \[bu] 2
148+asterisk 2
149+.IP \[bu] 2
150+asterisk 3
151+.LP
152+Asterisks loose:
153+.IP \[bu] 2
154+asterisk 1
155+.IP \[bu] 2
156+asterisk 2
157+.IP \[bu] 2
158+asterisk 3
159+.LP
160+Pluses tight:
161+.IP \[bu] 2
162+Plus 1
163+.IP \[bu] 2
164+Plus 2
165+.IP \[bu] 2
166+Plus 3
167+.LP
168+Pluses loose:
169+.IP \[bu] 2
170+Plus 1
171+.IP \[bu] 2
172+Plus 2
173+.IP \[bu] 2
174+Plus 3
175+.LP
176+Minuses tight:
177+.IP \[bu] 2
178+Minus 1
179+.IP \[bu] 2
180+Minus 2
181+.IP \[bu] 2
182+Minus 3
183+.LP
184+Minuses loose:
185+.IP \[bu] 2
186+Minus 1
187+.IP \[bu] 2
188+Minus 2
189+.IP \[bu] 2
190+Minus 3
191+.SH 2
192+Ordered
193+.LP
194+Tight:
195+.IP "1." 3
196+First
197+.IP "2." 3
198+Second
199+.IP "3." 3
200+Third
201+.LP
202+and:
203+.IP "1." 3
204+One
205+.IP "2." 3
206+Two
207+.IP "3." 3
208+Three
209+.LP
210+Loose using tabs:
211+.IP "1." 3
212+First
213+.IP "2." 3
214+Second
215+.IP "3." 3
216+Third
217+.LP
218+and using spaces:
219+.IP "1." 3
220+One
221+.IP "2." 3
222+Two
223+.IP "3." 3
224+Three
225+.LP
226+Multiple paragraphs:
227+.IP "1." 3
228+Item 1, graf one.
229+.RS 4
230+.LP
231+Item 1.
232+graf two.
233+The quick brown fox jumped over the lazy dog's
234+back.
235+.RE
236+.IP "2." 3
237+Item 2.
238+.IP "3." 3
239+Item 3.
240+.SH 2
241+Nested
242+.IP \[bu] 2
243+Tab
244+.RS 2
245+.IP \[bu] 2
246+Tab
247+.RS 2
248+.IP \[bu] 2
249+Tab
250+.RE
251+.RE
252+.LP
253+Here's another:
254+.IP "1." 3
255+First
256+.IP "2." 3
257+Second:
258+.RS 4
259+.IP \[bu] 2
260+Fee
261+.IP \[bu] 2
262+Fie
263+.IP \[bu] 2
264+Foe
265+.RE
266+.IP "3." 3
267+Third
268+.LP
269+Same thing but with paragraphs:
270+.IP "1." 3
271+First
272+.IP "2." 3
273+Second:
274+.RS 4
275+.IP \[bu] 2
276+Fee
277+.IP \[bu] 2
278+Fie
279+.IP \[bu] 2
280+Foe
281+.RE
282+.IP "3." 3
283+Third
284+.SH 2
285+Tabs and spaces
286+.IP \[bu] 2
287+this is a list item
288+indented with tabs
289+.IP \[bu] 2
290+this is a list item
291+indented with spaces
292+.RS 2
293+.IP \[bu] 2
294+this is an example list item
295+indented with tabs
296+.IP \[bu] 2
297+this is an example list item
298+indented with spaces
299+.RE
300+.SH 2
301+Fancy list markers
302+.IP "(2)" 4
303+begins with 2
304+.IP "(3)" 4
305+and now 3
306+.RS 4
307+.LP
308+with a continuation
309+.IP "iv." 4
310+sublist with roman numerals,
311+starting with 4
312+.IP " v." 4
313+more items
314+.RS 4
315+.IP "(A)" 4
316+a subsublist
317+.IP "(B)" 4
318+a subsublist
319+.RE
320+.RE
321+.LP
322+Nesting:
323+.IP "A." 3
324+Upper Alpha
325+.RS 4
326+.IP "I." 3
327+Upper Roman.
328+.RS 4
329+.IP "(6)" 4
330+Decimal start with 6
331+.RS 4
332+.IP "c)" 3
333+Lower alpha with paren
334+.RE
335+.RE
336+.RE
337+.LP
338+Autonumbering:
339+.IP "1." 3
340+Autonumber.
341+.IP "2." 3
342+More.
343+.RS 4
344+.IP "1." 3
345+Nested.
346+.RE
347+.LP
348+Should not be a list item:
349+.LP
350+M.A.\ 2007
351+.LP
352+B.
353+Williams
354+.PP
355+ * * * * *
356+.SH 1
357+Definition Lists
358+.LP
359+Tight using spaces:
360+.XP
361+.B "apple"
362+\~\~red fruit
363+.RS
364+.RE
365+.XP
366+.B "orange"
367+\~\~orange fruit
368+.RS
369+.RE
370+.XP
371+.B "banana"
372+\~\~yellow fruit
373+.RS
374+.RE
375+.LP
376+Tight using tabs:
377+.XP
378+.B "apple"
379+\~\~red fruit
380+.RS
381+.RE
382+.XP
383+.B "orange"
384+\~\~orange fruit
385+.RS
386+.RE
387+.XP
388+.B "banana"
389+\~\~yellow fruit
390+.RS
391+.RE
392+.LP
393+Loose:
394+.XP
395+.B "apple"
396+\~\~red fruit
397+.RS
398+.RE
399+.XP
400+.B "orange"
401+\~\~orange fruit
402+.RS
403+.RE
404+.XP
405+.B "banana"
406+\~\~yellow fruit
407+.RS
408+.RE
409+.LP
410+Multiple blocks with italics:
411+.XP
412+.B "\f[I]apple\f[]"
413+\~\~red fruit
414+.RS
415+.LP
416+contains seeds,
417+crisp, pleasant to taste
418+.RE
419+.XP
420+.B "\f[I]orange\f[]"
421+\~\~orange fruit
422+.RS
423+.IP
424+.nf
425+\f[C]
426+{\ orange\ code\ block\ }
427+\f[]
428+.fi
429+.RS
430+.LP
431+orange block quote
432+.RE
433+.RE
434+.LP
435+Multiple definitions, tight:
436+.XP
437+.B "apple"
438+\~\~red fruit
439+.RS
440+.RE
441+computer
442+.RS
443+.RE
444+.XP
445+.B "orange"
446+\~\~orange fruit
447+.RS
448+.RE
449+bank
450+.RS
451+.RE
452+.LP
453+Multiple definitions, loose:
454+.XP
455+.B "apple"
456+\~\~red fruit
457+.RS
458+.RE
459+computer
460+.RS
461+.RE
462+.XP
463+.B "orange"
464+\~\~orange fruit
465+.RS
466+.RE
467+bank
468+.RS
469+.RE
470+.LP
471+Blank line after term, indented marker, alternate markers:
472+.XP
473+.B "apple"
474+\~\~red fruit
475+.RS
476+.RE
477+computer
478+.RS
479+.RE
480+.XP
481+.B "orange"
482+\~\~orange fruit
483+.RS
484+.IP "1." 3
485+sublist
486+.IP "2." 3
487+sublist
488+.RE
489+.SH 1
490+HTML Blocks
491+.LP
492+Simple block on one line:
493+foo
494+.LP
495+And nested without indentation:
496+.LP
497+foo
498+bar
499+.LP
500+Interpreted markdown in a table:
501+This is \f[I]emphasized\f[]
502+And this is \f[B]strong\f[]
503+.LP
504+Here's a simple block:
505+.LP
506+foo
507+.LP
508+This should be a code block, though:
509+.IP
510+.nf
511+\f[C]
512+<div>
513+\ \ \ \ foo
514+</div>
515+\f[]
516+.fi
517+.LP
518+As should this:
519+.IP
520+.nf
521+\f[C]
522+<div>foo</div>
523+\f[]
524+.fi
525+.LP
526+Now, nested:
527+foo
528+.LP
529+This should just be an HTML comment:
530+.LP
531+Multiline:
532+.LP
533+Code block:
534+.IP
535+.nf
536+\f[C]
537+<!\-\-\ Comment\ \-\->
538+\f[]
539+.fi
540+.LP
541+Just plain comment, with trailing spaces on the line:
542+.LP
543+Code:
544+.IP
545+.nf
546+\f[C]
547+<hr\ />
548+\f[]
549+.fi
550+.LP
551+Hr's:
552+.PP
553+ * * * * *
554+.SH 1
555+Inline Markup
556+.LP
557+This is \f[I]emphasized\f[], and so \f[I]is this\f[].
558+.LP
559+This is \f[B]strong\f[], and so \f[B]is this\f[].
560+.LP
561+An \f[I]emphasized link\**\f[].
562+.FS
563+/url
564+.FE
565+.LP
566+\f[B]\f[I]This is strong and em.\f[]\f[]
567+.LP
568+So is \f[B]\f[I]this\f[]\f[] word.
569+.LP
570+\f[B]\f[I]This is strong and em.\f[]\f[]
571+.LP
572+So is \f[B]\f[I]this\f[]\f[] word.
573+.LP
574+This is code: \f[C]>\f[], \f[C]$\f[], \f[C]\\\f[], \f[C]\\$\f[],
575+\f[C]<html>\f[].
576+.LP
577+[STRIKEOUT:This is \f[I]strikeout\f[].]
578+.LP
579+Superscripts: a^bc^d a^\f[I]hello\f[]^ a^hello\ there^.
580+.LP
581+Subscripts: H~2~O, H~23~O, H~many\ of\ them~O.
582+.LP
583+These should not be superscripts or subscripts,
584+because of the unescaped spaces: a^b c^d, a~b c~d.
585+.PP
586+ * * * * *
587+.SH 1
588+Smart quotes, ellipses, dashes
589+.LP
590+\[lq]Hello,\[rq] said the spider.
591+\[lq]`Shelob' is my name.\[rq]
592+.LP
593+`A', `B', and `C' are letters.
594+.LP
595+`Oak,' `elm,' and `beech' are names of trees.
596+So is `pine.'
597+.LP
598+`He said, \[lq]I want to go.\[rq]' Were you alive in the
599+70's?
600+.LP
601+Here is some quoted `\f[C]code\f[]' and a \[lq]quoted link\**\[rq].
602+.FS
603+http://example.com/?foo=1&bar=2
604+.FE
605+.LP
606+Some dashes: one\[em]two \[em] three\[em]four \[em] five.
607+.LP
608+Dashes between numbers: 5\[en]7, 255\[en]66, 1987\[en]1999.
609+.LP
610+Ellipses\&...and\&...and\&....
611+.PP
612+ * * * * *
613+.SH 1
614+LaTeX
615+.IP \[bu] 2
616+.IP \[bu] 2
617+|2 + 2 = 4|
618+.IP \[bu] 2
619+|x \[u2208] y|
620+.IP \[bu] 2
621+|alpha \[u2227] omega|
622+.IP \[bu] 2
623+|223|
624+.IP \[bu] 2
625+|p|\-Tree
626+.IP \[bu] 2
627+Here's some display math:
628+.EQ
629+d over {d x} f ( x ) = lim sub {h -> 0} {f ( x + h ) \[u2212] f ( x )} over h
630+.EN
631+.IP \[bu] 2
632+Here's one that has a line break in it: |alpha + omega times x sup 2|.
633+.LP
634+These shouldn't be math:
635+.IP \[bu] 2
636+To get the famous equation, write \f[C]$e\ =\ mc^2$\f[].
637+.IP \[bu] 2
638+$22,000 is a \f[I]lot\f[] of money.
639+So is $34,000.
640+(It worked if \[lq]lot\[rq] is emphasized.)
641+.IP \[bu] 2
642+Shoes ($20) and socks ($5).
643+.IP \[bu] 2
644+Escaped \f[C]$\f[]: $73 \f[I]this should be emphasized\f[] 23$.
645+.LP
646+Here's a LaTeX table:
647+.PP
648+ * * * * *
649+.SH 1
650+Special Characters
651+.LP
652+Here is some unicode:
653+.IP \[bu] 2
654+I hat: Î
655+.IP \[bu] 2
656+o umlaut: ö
657+.IP \[bu] 2
658+section: §
659+.IP \[bu] 2
660+set membership: ∈
661+.IP \[bu] 2
662+copyright: ©
663+.LP
664+AT&T has an ampersand in their name.
665+.LP
666+AT&T is another way to write it.
667+.LP
668+This & that.
669+.LP
670+4 < 5.
671+.LP
672+6 > 5.
673+.LP
674+Backslash: \\
675+.LP
676+Backtick: `
677+.LP
678+Asterisk: *
679+.LP
680+Underscore: _
681+.LP
682+Left brace: {
683+.LP
684+Right brace: }
685+.LP
686+Left bracket: [
687+.LP
688+Right bracket: ]
689+.LP
690+Left paren: (
691+.LP
692+Right paren: )
693+.LP
694+Greater\-than: >
695+.LP
696+Hash: #
697+.LP
698+Period: .
699+.LP
700+Bang: !
701+.LP
702+Plus: +
703+.LP
704+Minus: \-
705+.PP
706+ * * * * *
707+.SH 1
708+Links
709+.SH 2
710+Explicit
711+.LP
712+Just a URL\**.
713+.FS
714+/url/
715+.FE
716+.LP
717+URL and title\**.
718+.FS
719+/url/
720+.FE
721+.LP
722+URL and title\**.
723+.FS
724+/url/
725+.FE
726+.LP
727+URL and title\**.
728+.FS
729+/url/
730+.FE
731+.LP
732+URL and title\**
733+.FS
734+/url/
735+.FE
736+.LP
737+URL and title\**
738+.FS
739+/url/
740+.FE
741+.LP
742+with_underscore\**
743+.FS
744+/url/with_underscore
745+.FE
746+.LP
747+Email link\**
748+.FS
749+mailto:nobody\@nowhere.net
750+.FE
751+.LP
752+Empty\**.
753+.FS
754+.FE
755+.SH 2
756+Reference
757+.LP
758+Foo bar\**.
759+.FS
760+/url/
761+.FE
762+.LP
763+Foo bar\**.
764+.FS
765+/url/
766+.FE
767+.LP
768+Foo bar\**.
769+.FS
770+/url/
771+.FE
772+.LP
773+With embedded [brackets]\**.
774+.FS
775+/url/
776+.FE
777+.LP
778+b\**
779+.FS
780+/url/
781+.FE
782+by itself should be a link.
783+.LP
784+Indented once\**.
785+.FS
786+/url
787+.FE
788+.LP
789+Indented twice\**.
790+.FS
791+/url
792+.FE
793+.LP
794+Indented thrice\**.
795+.FS
796+/url
797+.FE
798+.LP
799+This should [not][] be a link.
800+.IP
801+.nf
802+\f[C]
803+[not]:\ /url
804+\f[]
805+.fi
806+.LP
807+Foo bar\**.
808+.FS
809+/url/
810+.FE
811+.LP
812+Foo biz\**.
813+.FS
814+/url/
815+.FE
816+.SH 2
817+With ampersands
818+.LP
819+Here's a link with an ampersand in the URL\**.
820+.FS
821+http://example.com/?foo=1&bar=2
822+.FE
823+.LP
824+Here's a link with an amersand in the link text: AT&T\**.
825+.FS
826+http://att.com/
827+.FE
828+.LP
829+Here's an inline link\**.
830+.FS
831+/script?foo=1&bar=2
832+.FE
833+.LP
834+Here's an inline link in pointy braces\**.
835+.FS
836+/script?foo=1&bar=2
837+.FE
838+.SH 2
839+Autolinks
840+.LP
841+With an ampersand: <http://example.com/?foo=1&bar=2>
842+.IP \[bu] 2
843+In a list?
844+.IP \[bu] 2
845+<http://example.com/>
846+.IP \[bu] 2
847+It should.
848+.LP
849+An e\-mail address: <nobody@nowhere.net>
850+.RS
851+.LP
852+Blockquoted: <http://example.com/>
853+.RE
854+.LP
855+Auto\-links should not occur here: \f[C]<http://example.com/>\f[]
856+.IP
857+.nf
858+\f[C]
859+or\ here:\ <http://example.com/>
860+\f[]
861+.fi
862+.PP
863+ * * * * *
864+.SH 1
865+Images
866+.LP
867+From \[lq]Voyage dans la Lune\[rq] by Georges Melies (1902):
868+.LP
869+[IMAGE: lalune\**]
870+.FS
871+lalune.jpg
872+.FE
873+.LP
874+Here is a movie [IMAGE: movie\**]
875+.FS
876+movie.jpg
877+.FE
878+icon.
879+.PP
880+ * * * * *
881+.SH 1
882+Footnotes
883+.LP
884+Here is a footnote reference,\**
885+.FS
886+.LP
887+Here is the footnote.
888+It can go anywhere after the footnote
889+reference.
890+It need not be placed at the end of the document.
891+.FE
892+and another.\**
893+.FS
894+.LP
895+Here's the long note.
896+This one contains multiple
897+blocks.
898+.LP
899+Subsequent blocks are indented to show that they belong to the
900+footnote (as with list items).
901+.IP
902+.nf
903+\f[C]
904+\ \ {\ <code>\ }
905+\f[]
906+.fi
907+.LP
908+If you want, you can indent every line, but you can also be
909+lazy and just indent the first line of each block.
910+.FE
911+This should \f[I]not\f[] be a footnote reference, because it
912+contains a space.[^my note] Here is an inline note.\**
913+.FS
914+.LP
915+This
916+is \f[I]easier\f[] to type.
917+Inline notes may contain
918+links\**
919+.FS
920+http://google.com
921+.FE
922+and \f[C]]\f[] verbatim characters,
923+as well as [bracketed text].
924+.FE
925+.RS
926+.LP
927+Notes can go in quotes.\**
928+.FS
929+.LP
930+In quote.
931+.FE
932+.RE
933+.IP "1." 3
934+And in list items.\**
935+.FS
936+.LP
937+In list.
938+.FE
939+.LP
940+This paragraph should not be part of the note, as it is not indented.