git.iko.soy/typeable/pandoc
on master help sign in
Reverted back to state as of r1062. The template haskell changes

are more trouble than they're worth. git-svn-id: https://pandoc.googlecode.com/svn/trunk@1064 788f1e2b-df1e-0410-8736-df70ead52e1b

commitfe684764e68e7eda281192f1fdd637a5bdb50e43 authorfiddlosopher <fiddlosopher@788f1e2b-df1e-0410-8736-df70ead52e1b> dateSat 3 Nov 2007, 23:27 (19 years ago) parent4a841bf
50 files changed+7195−55
INSTALL+4−1 Makefile+8−2 pandoc.cabal+6−6 data/ASCIIMathML.js → src/ASCIIMathML.js Main.hs → src/Main.hs src/Text/Pandoc.hs+110 src/Text/Pandoc/Blocks.hs+145 src/Text/Pandoc/CharacterReferences.hs+327 src/Text/Pandoc/Definition.hs+116 src/Text/Pandoc/Readers/HTML.hs+496 src/Text/Pandoc/Readers/LaTeX.hs+651 src/Text/Pandoc/Readers/Markdown.hs+909 src/Text/Pandoc/Readers/RST.hs+640 src/Text/Pandoc/Shared.hs+792 src/Text/Pandoc/UTF8.hs+45 src/Text/Pandoc/Writers/ConTeXt.hs+248 src/Text/Pandoc/Writers/Docbook.hs+299 src/Text/Pandoc/Writers/HTML.hs+458 src/Text/Pandoc/Writers/LaTeX.hs+310 src/Text/Pandoc/Writers/Man.hs+293 src/Text/Pandoc/Writers/Markdown.hs+373 src/Text/Pandoc/Writers/RST.hs+325 src/Text/Pandoc/Writers/RTF.hs+286 data/headers/ConTeXtHeader → src/headers/ConTeXtHeader data/headers/DocbookHeader → src/headers/DocbookHeader data/headers/LaTeXHeader → src/headers/LaTeXHeader data/headers/RTFHeader → src/headers/RTFHeader data/headers/S5Header → src/headers/S5Header src/templates/ASCIIMathML.hs+7 src/templates/DefaultHeaders.hs+52 src/templates/Makefile+20 src/templates/S5.hs+133 src/templates/fillTemplates.pl+131 data/ui/default/blank.gif → src/ui/default/blank.gif data/ui/default/bodybg.gif → src/ui/default/bodybg.gif data/ui/default/framing.css → src/ui/default/framing.css data/ui/default/iepngfix.htc → src/ui/default/iepngfix.htc data/ui/default/opera.css → src/ui/default/opera.css data/ui/default/outline.css → src/ui/default/outline.css data/ui/default/pretty.css → src/ui/default/pretty.css data/ui/default/print.css → src/ui/default/print.css data/ui/default/s5-core.css → src/ui/default/s5-core.css data/ui/default/slides.css → src/ui/default/slides.css data/ui/default/slides.js → src/ui/default/slides.js wrappers/common.sh → src/wrappers/common.sh wrappers/hsmarkdown.in → src/wrappers/hsmarkdown.in wrappers/html2markdown.in → src/wrappers/html2markdown.in wrappers/markdown2pdf.in → src/wrappers/markdown2pdf.in wrappers/tempdir.sh → src/wrappers/tempdir.sh tests/s5.fancy.html+11−46
INSTALL+4−1
@@ -107,12 +107,15 @@ but are documented here for packagers and developers:
107107
108108* `configure`: Performs the needed preprocessing to create a proper
109109 Cabal package for Pandoc:
110+ - Builds `ASCIIMathML.hs`, `DefaultHeaders.hs`, and `S5.hs`
111+ from templates in `src/templates` and data in `src/ASCIIMathML.js`,
112+ `src/ui`, and `src/headers`.
110113 - Stores values of relevant environment variables in `vars` for
111114 persistence.
112115 - Runs Cabal's "configure" command.
113116* `build-exec`: Builds `pandoc` executable (using Cabal's "build"
114117 command) and creates the wrappers `html2markdown` and `markdown2pdf`
115− from templates in `wrappers`.
118+ from templates in `src/wrappers`.
116119* `build-doc`: Builds program documentation (e.g. `README.html`).
117120* `build-lib-doc`: Builds Haddock documentation for Pandoc libraries.
118121* `install-doc`, `uninstall-doc`: Installs/uninstalls user documentation
Makefile+8−2
@@ -4,7 +4,7 @@
44# Constant names and commands in source tree
55#-------------------------------------------------------------------------------
66CABAL := pandoc.cabal
7−SRCDIR := .
7+SRCDIR := src
88MANDIR := man
99TESTDIR := tests
1010BUILDDIR := dist
@@ -91,6 +91,10 @@ all: build-program
9191%.1: %.1.md $(MAIN)
9292 ./$(MAIN) -s -S -w man $< >$@ || rm -f $@
9393
94+.PHONY: templates
95+templates: $(SRCDIR)/templates
96+ $(MAKE) -C $(SRCDIR)/templates
97+
9498define generate-shell-script
9599echo >&2 "Generating $@..."; \
96100awk ' \
@@ -114,7 +118,7 @@ $(WRAPPERS): %: $(SRCDIR)/wrappers/%.in $(SRCDIR)/wrappers/*.sh
114118
115119.PHONY: configure
116120cleanup_files+=Setup.hi Setup.o $(BUILDCMD) $(BUILDVARS)
117−configure: $(BUILDCONF)
121+configure: $(BUILDCONF) templates
118122$(BUILDCONF): $(CABAL)
119123 ghc -package Cabal Setup.hs -o $(BUILDCMD)
120124 $(BUILDCMD) configure --prefix=$(PREFIX)
@@ -279,6 +283,7 @@ cleanup_files+=$(tarball)
279283tarball: $(tarball)
280284$(tarball):
281285 svn export . $(PKGID)
286+ $(MAKE) -C $(PKGID) templates
282287 $(MAKE) -C $(PKGID) wrappers
283288 tar cvzf $(tarball) $(PKGID)
284289 -rm -rf $(PKGID)
@@ -338,5 +343,6 @@ distclean: clean
338343 fi
339344
340345clean:
346+ make -C $(SRCDIR)/templates clean
341347 -if [ -f $(BUILDCONF) ]; then $(BUILDCMD) clean; fi
342348 -rm -rf $(cleanup_files)
pandoc.cabal+6−6
@@ -31,14 +31,15 @@ Description: Pandoc is a Haskell library for converting from one markup
3131 which convert this native representation into a target
3232 format. Thus, adding an input or output format requires
3333 only adding a reader or writer.
34−Build-Depends: base, parsec, xhtml, mtl, regex-compat, network, template-haskell, filepath
35−Hs-Source-Dirs: .
34+Build-Depends: base, parsec, xhtml, mtl, regex-compat, network
35+Hs-Source-Dirs: src
3636Exposed-Modules: Text.Pandoc,
3737 Text.Pandoc.Blocks,
3838 Text.Pandoc.Definition,
3939 Text.Pandoc.CharacterReferences,
4040 Text.Pandoc.Shared,
4141 Text.Pandoc.UTF8,
42+ Text.Pandoc.ASCIIMathML,
4243 Text.Pandoc.Readers.HTML,
4344 Text.Pandoc.Readers.LaTeX,
4445 Text.Pandoc.Readers.Markdown,
@@ -53,10 +54,9 @@ Exposed-Modules: Text.Pandoc,
5354 Text.Pandoc.Writers.RST,
5455 Text.Pandoc.Writers.RTF,
5556 Text.Pandoc.Writers.S5
56−Other-Modules: Text.Pandoc.Include
57−Ghc-Options: -O0 -fth
57+Ghc-Options: -O0
5858
5959Executable: pandoc
60−Hs-Source-Dirs: .
60+Hs-Source-Dirs: src
6161Main-Is: Main.hs
62−Ghc-Options: -O0 -fth
62+Ghc-Options: -O0
data/ASCIIMathML.js → src/ASCIIMathML.jsrenamed

The content is the same.

Main.hs → src/Main.hsrenamed

The content is the same.

src/Text/Pandoc.hsnew file+110
@@ -0,0 +1,110 @@
1+{-
2+Copyright (C) 2006-7 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
21+ Copyright : Copyright (C) 2006-7 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+This helper module exports the main writers, readers, and data
29+structure definitions from the Pandoc libraries.
30+
31+A typical application will chain together a reader and a writer
32+to convert strings from one format to another. For example, the
33+following simple program will act as a filter converting markdown
34+fragments to reStructuredText, using reference-style links instead of
35+inline links:
36+
37+> module Main where
38+> import Text.Pandoc
39+>
40+> markdownToRST :: String -> String
41+> markdownToRST = toUTF8 .
42+> (writeRST defaultWriterOptions {writerReferenceLinks = True}) .
43+> (readMarkdown defaultParserState) . fromUTF8
44+>
45+> main = interact markdownToRST
46+
47+-}
48+
49+module Text.Pandoc
50+ (
51+ -- * Definitions
52+ module Text.Pandoc.Definition
53+ -- * Readers: converting /to/ Pandoc format
54+ , readMarkdown
55+ , readRST
56+ , readLaTeX
57+ , readHtml
58+ -- * Parser state used in readers
59+ , ParserState (..)
60+ , defaultParserState
61+ , ParserContext (..)
62+ , QuoteContext (..)
63+ , KeyTable
64+ , NoteTable
65+ , HeaderType (..)
66+ -- * Writers: converting /from/ Pandoc format
67+ , writeMarkdown
68+ , writeRST
69+ , writeLaTeX
70+ , writeConTeXt
71+ , writeHtml
72+ , writeHtmlString
73+ , writeS5
74+ , writeS5String
75+ , writeDocbook
76+ , writeMan
77+ , writeRTF
78+ , prettyPandoc
79+ -- * Writer options used in writers
80+ , WriterOptions (..)
81+ , defaultWriterOptions
82+ -- * Default headers for various output formats
83+ , module Text.Pandoc.Writers.DefaultHeaders
84+ -- * Functions for converting to and from UTF-8
85+ , module Text.Pandoc.UTF8
86+ -- * Version
87+ , pandocVersion
88+ ) where
89+
90+import Text.Pandoc.Definition
91+import Text.Pandoc.Readers.Markdown
92+import Text.Pandoc.Readers.RST
93+import Text.Pandoc.Readers.LaTeX
94+import Text.Pandoc.Readers.HTML
95+import Text.Pandoc.Writers.Markdown
96+import Text.Pandoc.Writers.RST
97+import Text.Pandoc.Writers.LaTeX
98+import Text.Pandoc.Writers.ConTeXt
99+import Text.Pandoc.Writers.HTML
100+import Text.Pandoc.Writers.S5
101+import Text.Pandoc.Writers.Docbook
102+import Text.Pandoc.Writers.Man
103+import Text.Pandoc.Writers.RTF
104+import Text.Pandoc.Writers.DefaultHeaders
105+import Text.Pandoc.UTF8
106+import Text.Pandoc.Shared
107+
108+-- | Version number of pandoc library.
109+pandocVersion :: String
110+pandocVersion = "0.45"
src/Text/Pandoc/Blocks.hsnew file+145
@@ -0,0 +1,145 @@
1+{-
2+Copyright (C) 2007 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.Blocks
21+ Copyright : Copyright (C) 2007 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+Functions for the manipulation of fixed-width blocks of text.
29+These are used in the construction of plain-text tables.
30+-}
31+
32+module Text.Pandoc.Blocks
33+ (
34+ TextBlock (..),
35+ docToBlock,
36+ blockToDoc,
37+ widthOfBlock,
38+ heightOfBlock,
39+ hcatBlocks,
40+ hsepBlocks,
41+ centerAlignBlock,
42+ leftAlignBlock,
43+ rightAlignBlock
44+ )
45+where
46+import Text.PrettyPrint
47+import Data.List ( intersperse )
48+
49+-- | A fixed-width block of text. Parameters are width of block,
50+-- height of block, and list of lines.
51+data TextBlock = TextBlock Int Int [String]
52+instance Show TextBlock where
53+ show x = show $ blockToDoc x
54+
55+-- | Break lines in a list of lines so that none are greater than
56+-- a given width.
57+breakLines :: Int -- ^ Maximum length of lines.
58+ -> [String] -- ^ List of lines.
59+ -> [String]
60+breakLines width [] = []
61+breakLines width (l:ls) =
62+ if length l > width
63+ then (take width l):(breakLines width ((drop width l):ls))
64+ else l:(breakLines width ls)
65+
66+-- | Convert a @Doc@ element into a @TextBlock@ with a specified width.
67+docToBlock :: Int -- ^ Width of text block.
68+ -> Doc -- ^ @Doc@ to convert.
69+ -> TextBlock
70+docToBlock width doc =
71+ let rendered = renderStyle (style {lineLength = width,
72+ ribbonsPerLine = 1}) doc
73+ lns = breakLines width $ lines rendered
74+ in TextBlock width (length lns) lns
75+
76+-- | Convert a @TextBlock@ to a @Doc@ element.
77+blockToDoc :: TextBlock -> Doc
78+blockToDoc (TextBlock _ _ lns) =
79+ if null lns
80+ then empty
81+ else vcat $ map text lns
82+
83+-- | Returns width of a @TextBlock@ (number of columns).
84+widthOfBlock :: TextBlock -> Int
85+widthOfBlock (TextBlock width _ _) = width
86+
87+-- | Returns height of a @TextBlock@ (number of rows).
88+heightOfBlock :: TextBlock -> Int
89+heightOfBlock (TextBlock _ height _) = height
90+
91+-- | Pads a string out to a given width using spaces.
92+hPad :: Int -- ^ Desired width.
93+ -> String -- ^ String to pad.
94+ -> String
95+hPad width line =
96+ let lineLength = length line
97+ in if lineLength <= width
98+ then line ++ replicate (width - lineLength) ' '
99+ else take width line
100+
101+-- | Concatenates a list of @TextBlock@s into a new @TextBlock@ in
102+-- which they appear side by side.
103+hcatBlocks :: [TextBlock] -> TextBlock
104+hcatBlocks [] = TextBlock 0 0 []
105+hcatBlocks [x] = x -- This is not redundant! We don't want last item hPad'd.
106+hcatBlocks ((TextBlock width1 height1 lns1):xs) =
107+ let (TextBlock width2 height2 lns2) = hcatBlocks xs
108+ height = max height1 height2
109+ width = width1 + width2
110+ lns1' = map (hPad width1) $ lns1 ++ replicate (height - height1) ""
111+ lns2' = lns2 ++ replicate (height - height2) ""
112+ lns = zipWith (++) lns1' lns2'
113+ in TextBlock width height lns
114+
115+-- | Like @hcatBlocks@, but inserts space between the @TextBlock@s.
116+hsepBlocks :: [TextBlock] -> TextBlock
117+hsepBlocks = hcatBlocks . (intersperse (TextBlock 1 1 [" "]))
118+
119+isWhitespace x = x `elem` " \t"
120+
121+-- | Left-aligns the contents of a @TextBlock@ within the block.
122+leftAlignBlock :: TextBlock -> TextBlock
123+leftAlignBlock (TextBlock width height lns) =
124+ TextBlock width height $ map (dropWhile isWhitespace) lns
125+
126+-- | Right-aligns the contents of a @TextBlock@ within the block.
127+rightAlignBlock :: TextBlock -> TextBlock
128+rightAlignBlock (TextBlock width height lns) =
129+ let rightAlignLine ln =
130+ let (spaces, rest) = span isWhitespace $ reverse $ hPad width ln
131+ in reverse (rest ++ spaces)
132+ in TextBlock width height $ map rightAlignLine lns
133+
134+-- | Centers the contents of a @TextBlock@ within the block.
135+centerAlignBlock :: TextBlock -> TextBlock
136+centerAlignBlock (TextBlock width height lns) =
137+ let centerAlignLine ln =
138+ let ln' = hPad width ln
139+ (startSpaces, rest) = span isWhitespace ln'
140+ endSpaces = takeWhile isWhitespace (reverse ln')
141+ numSpaces = length (startSpaces ++ endSpaces)
142+ startSpaces' = replicate (quot numSpaces 2) ' '
143+ in startSpaces' ++ rest
144+ in TextBlock width height $ map centerAlignLine lns
145+
src/Text/Pandoc/CharacterReferences.hsnew file+327
@@ -0,0 +1,327 @@
1+{-
2+Copyright (C) 2006-7 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.CharacterReferences
21+ Copyright : Copyright (C) 2006-7 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+Functions for parsing character references.
29+-}
30+module Text.Pandoc.CharacterReferences (
31+ characterReference,
32+ decodeCharacterReferences,
33+ ) where
34+import Data.Char ( chr )
35+import Text.ParserCombinators.Parsec
36+import qualified Data.Map as Map
37+
38+-- | Parse character entity.
39+characterReference :: GenParser Char st Char
40+characterReference = try $ do
41+ st <- char '&'
42+ character <- numRef <|> entity
43+ end <- char ';'
44+ return character
45+
46+numRef :: GenParser Char st Char
47+numRef = do
48+ char '#'
49+ num <- hexNum <|> decNum
50+ return $ chr $ num
51+
52+hexNum :: GenParser Char st Int
53+hexNum = oneOf "Xx" >> many1 hexDigit >>= return . read . ("0x" ++)
54+
55+decNum :: GenParser Char st Int
56+decNum = many1 digit >>= return . read
57+
58+entity :: GenParser Char st Char
59+entity = do
60+ body <- many1 alphaNum
61+ return $ Map.findWithDefault '?' body entityTable
62+
63+-- | Convert entities in a string to characters.
64+decodeCharacterReferences :: String -> String
65+decodeCharacterReferences str =
66+ case parse (many (characterReference <|> anyChar)) str str of
67+ Left err -> error $ "\nError: " ++ show err
68+ Right result -> result
69+
70+entityTable :: Map.Map String Char
71+entityTable = Map.fromList entityTableList
72+
73+entityTableList :: [(String, Char)]
74+entityTableList = [
75+ ("quot", chr 34),
76+ ("amp", chr 38),
77+ ("lt", chr 60),
78+ ("gt", chr 62),
79+ ("nbsp", chr 160),
80+ ("iexcl", chr 161),
81+ ("cent", chr 162),
82+ ("pound", chr 163),
83+ ("curren", chr 164),
84+ ("yen", chr 165),
85+ ("brvbar", chr 166),
86+ ("sect", chr 167),
87+ ("uml", chr 168),
88+ ("copy", chr 169),
89+ ("ordf", chr 170),
90+ ("laquo", chr 171),
91+ ("not", chr 172),
92+ ("shy", chr 173),
93+ ("reg", chr 174),
94+ ("macr", chr 175),
95+ ("deg", chr 176),
96+ ("plusmn", chr 177),
97+ ("sup2", chr 178),
98+ ("sup3", chr 179),
99+ ("acute", chr 180),
100+ ("micro", chr 181),
101+ ("para", chr 182),
102+ ("middot", chr 183),
103+ ("cedil", chr 184),
104+ ("sup1", chr 185),
105+ ("ordm", chr 186),
106+ ("raquo", chr 187),
107+ ("frac14", chr 188),
108+ ("frac12", chr 189),
109+ ("frac34", chr 190),
110+ ("iquest", chr 191),
111+ ("Agrave", chr 192),
112+ ("Aacute", chr 193),
113+ ("Acirc", chr 194),
114+ ("Atilde", chr 195),
115+ ("Auml", chr 196),
116+ ("Aring", chr 197),
117+ ("AElig", chr 198),
118+ ("Ccedil", chr 199),
119+ ("Egrave", chr 200),
120+ ("Eacute", chr 201),
121+ ("Ecirc", chr 202),
122+ ("Euml", chr 203),
123+ ("Igrave", chr 204),
124+ ("Iacute", chr 205),
125+ ("Icirc", chr 206),
126+ ("Iuml", chr 207),
127+ ("ETH", chr 208),
128+ ("Ntilde", chr 209),
129+ ("Ograve", chr 210),
130+ ("Oacute", chr 211),
131+ ("Ocirc", chr 212),
132+ ("Otilde", chr 213),
133+ ("Ouml", chr 214),
134+ ("times", chr 215),
135+ ("Oslash", chr 216),
136+ ("Ugrave", chr 217),
137+ ("Uacute", chr 218),
138+ ("Ucirc", chr 219),
139+ ("Uuml", chr 220),
140+ ("Yacute", chr 221),
141+ ("THORN", chr 222),
142+ ("szlig", chr 223),
143+ ("agrave", chr 224),
144+ ("aacute", chr 225),
145+ ("acirc", chr 226),
146+ ("atilde", chr 227),
147+ ("auml", chr 228),
148+ ("aring", chr 229),
149+ ("aelig", chr 230),
150+ ("ccedil", chr 231),
151+ ("egrave", chr 232),
152+ ("eacute", chr 233),
153+ ("ecirc", chr 234),
154+ ("euml", chr 235),
155+ ("igrave", chr 236),
156+ ("iacute", chr 237),
157+ ("icirc", chr 238),
158+ ("iuml", chr 239),
159+ ("eth", chr 240),
160+ ("ntilde", chr 241),
161+ ("ograve", chr 242),
162+ ("oacute", chr 243),
163+ ("ocirc", chr 244),
164+ ("otilde", chr 245),
165+ ("ouml", chr 246),
166+ ("divide", chr 247),
167+ ("oslash", chr 248),
168+ ("ugrave", chr 249),
169+ ("uacute", chr 250),
170+ ("ucirc", chr 251),
171+ ("uuml", chr 252),
172+ ("yacute", chr 253),
173+ ("thorn", chr 254),
174+ ("yuml", chr 255),
175+ ("OElig", chr 338),
176+ ("oelig", chr 339),
177+ ("Scaron", chr 352),
178+ ("scaron", chr 353),
179+ ("Yuml", chr 376),
180+ ("fnof", chr 402),
181+ ("circ", chr 710),
182+ ("tilde", chr 732),
183+ ("Alpha", chr 913),
184+ ("Beta", chr 914),
185+ ("Gamma", chr 915),
186+ ("Delta", chr 916),
187+ ("Epsilon", chr 917),
188+ ("Zeta", chr 918),
189+ ("Eta", chr 919),
190+ ("Theta", chr 920),
191+ ("Iota", chr 921),
192+ ("Kappa", chr 922),
193+ ("Lambda", chr 923),
194+ ("Mu", chr 924),
195+ ("Nu", chr 925),
196+ ("Xi", chr 926),
197+ ("Omicron", chr 927),
198+ ("Pi", chr 928),
199+ ("Rho", chr 929),
200+ ("Sigma", chr 931),
201+ ("Tau", chr 932),
202+ ("Upsilon", chr 933),
203+ ("Phi", chr 934),
204+ ("Chi", chr 935),
205+ ("Psi", chr 936),
206+ ("Omega", chr 937),
207+ ("alpha", chr 945),
208+ ("beta", chr 946),
209+ ("gamma", chr 947),
210+ ("delta", chr 948),
211+ ("epsilon", chr 949),
212+ ("zeta", chr 950),
213+ ("eta", chr 951),
214+ ("theta", chr 952),
215+ ("iota", chr 953),
216+ ("kappa", chr 954),
217+ ("lambda", chr 955),
218+ ("mu", chr 956),
219+ ("nu", chr 957),
220+ ("xi", chr 958),
221+ ("omicron", chr 959),
222+ ("pi", chr 960),
223+ ("rho", chr 961),
224+ ("sigmaf", chr 962),
225+ ("sigma", chr 963),
226+ ("tau", chr 964),
227+ ("upsilon", chr 965),
228+ ("phi", chr 966),
229+ ("chi", chr 967),
230+ ("psi", chr 968),
231+ ("omega", chr 969),
232+ ("thetasym", chr 977),
233+ ("upsih", chr 978),
234+ ("piv", chr 982),
235+ ("ensp", chr 8194),
236+ ("emsp", chr 8195),
237+ ("thinsp", chr 8201),
238+ ("zwnj", chr 8204),
239+ ("zwj", chr 8205),
240+ ("lrm", chr 8206),
241+ ("rlm", chr 8207),
242+ ("ndash", chr 8211),
243+ ("mdash", chr 8212),
244+ ("lsquo", chr 8216),
245+ ("rsquo", chr 8217),
246+ ("sbquo", chr 8218),
247+ ("ldquo", chr 8220),
248+ ("rdquo", chr 8221),
249+ ("bdquo", chr 8222),
250+ ("dagger", chr 8224),
251+ ("Dagger", chr 8225),
252+ ("bull", chr 8226),
253+ ("hellip", chr 8230),
254+ ("permil", chr 8240),
255+ ("prime", chr 8242),
256+ ("Prime", chr 8243),
257+ ("lsaquo", chr 8249),
258+ ("rsaquo", chr 8250),
259+ ("oline", chr 8254),
260+ ("frasl", chr 8260),
261+ ("euro", chr 8364),
262+ ("image", chr 8465),
263+ ("weierp", chr 8472),
264+ ("real", chr 8476),
265+ ("trade", chr 8482),
266+ ("alefsym", chr 8501),
267+ ("larr", chr 8592),
268+ ("uarr", chr 8593),
269+ ("rarr", chr 8594),
270+ ("darr", chr 8595),
271+ ("harr", chr 8596),
272+ ("crarr", chr 8629),
273+ ("lArr", chr 8656),
274+ ("uArr", chr 8657),
275+ ("rArr", chr 8658),
276+ ("dArr", chr 8659),
277+ ("hArr", chr 8660),
278+ ("forall", chr 8704),
279+ ("part", chr 8706),
280+ ("exist", chr 8707),
281+ ("empty", chr 8709),
282+ ("nabla", chr 8711),
283+ ("isin", chr 8712),
284+ ("notin", chr 8713),
285+ ("ni", chr 8715),
286+ ("prod", chr 8719),
287+ ("sum", chr 8721),
288+ ("minus", chr 8722),
289+ ("lowast", chr 8727),
290+ ("radic", chr 8730),
291+ ("prop", chr 8733),
292+ ("infin", chr 8734),
293+ ("ang", chr 8736),
294+ ("and", chr 8743),
295+ ("or", chr 8744),
296+ ("cap", chr 8745),
297+ ("cup", chr 8746),
298+ ("int", chr 8747),
299+ ("there4", chr 8756),
300+ ("sim", chr 8764),
301+ ("cong", chr 8773),
302+ ("asymp", chr 8776),
303+ ("ne", chr 8800),
304+ ("equiv", chr 8801),
305+ ("le", chr 8804),
306+ ("ge", chr 8805),
307+ ("sub", chr 8834),
308+ ("sup", chr 8835),
309+ ("nsub", chr 8836),
310+ ("sube", chr 8838),
311+ ("supe", chr 8839),
312+ ("oplus", chr 8853),
313+ ("otimes", chr 8855),
314+ ("perp", chr 8869),
315+ ("sdot", chr 8901),
316+ ("lceil", chr 8968),
317+ ("rceil", chr 8969),
318+ ("lfloor", chr 8970),
319+ ("rfloor", chr 8971),
320+ ("lang", chr 9001),
321+ ("rang", chr 9002),
322+ ("loz", chr 9674),
323+ ("spades", chr 9824),
324+ ("clubs", chr 9827),
325+ ("hearts", chr 9829),
326+ ("diams", chr 9830)
327+ ]
src/Text/Pandoc/Definition.hsnew file+116
@@ -0,0 +1,116 @@
1+{-
2+Copyright (C) 2006-7 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.Definition
21+ Copyright : Copyright (C) 2006-7 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+Definition of 'Pandoc' data structure for format-neutral representation
29+of documents.
30+-}
31+module Text.Pandoc.Definition where
32+
33+data Pandoc = Pandoc Meta [Block] deriving (Eq, Read, Show)
34+
35+-- | Bibliographic information for the document: title (list of 'Inline'),
36+-- authors (list of strings), date (string).
37+data Meta = Meta [Inline] -- title
38+ [String] -- authors
39+ String -- date
40+ deriving (Eq, Show, Read)
41+
42+-- | Alignment of a table column.
43+data Alignment = AlignLeft
44+ | AlignRight
45+ | AlignCenter
46+ | AlignDefault deriving (Eq, Show, Read)
47+
48+-- | List attributes.
49+type ListAttributes = (Int, ListNumberStyle, ListNumberDelim)
50+
51+-- | Style of list numbers.
52+data ListNumberStyle = DefaultStyle
53+ | Decimal
54+ | LowerRoman
55+ | UpperRoman
56+ | LowerAlpha
57+ | UpperAlpha deriving (Eq, Show, Read)
58+
59+-- | Delimiter of list numbers.
60+data ListNumberDelim = DefaultDelim
61+ | Period
62+ | OneParen
63+ | TwoParens deriving (Eq, Show, Read)
64+
65+-- | Block element.
66+data Block
67+ = Plain [Inline] -- ^ Plain text, not a paragraph
68+ | Para [Inline] -- ^ Paragraph
69+ | CodeBlock String -- ^ Code block (literal)
70+ | RawHtml String -- ^ Raw HTML block (literal)
71+ | BlockQuote [Block] -- ^ Block quote (list of blocks)
72+ | OrderedList ListAttributes [[Block]] -- ^ Ordered list (attributes
73+ -- and a list of items, each a list of blocks)
74+ | BulletList [[Block]] -- ^ Bullet list (list of items, each
75+ -- a list of blocks)
76+ | DefinitionList [([Inline],[Block])] -- ^ Definition list
77+ -- (list of items, each a pair of an inline list,
78+ -- the term, and a block list)
79+ | Header Int [Inline] -- ^ Header - level (integer) and text (inlines)
80+ | HorizontalRule -- ^ Horizontal rule
81+ | Table [Inline] [Alignment] [Float] [[Block]] [[[Block]]] -- ^ Table,
82+ -- with caption, column alignments,
83+ -- relative column widths, column headers
84+ -- (each a list of blocks), and rows
85+ -- (each a list of lists of blocks)
86+ | Null -- ^ Nothing
87+ deriving (Eq, Read, Show)
88+
89+-- | Type of quotation marks to use in Quoted inline.
90+data QuoteType = SingleQuote | DoubleQuote deriving (Show, Eq, Read)
91+
92+type Target = (String, String) -- ^ Link target (URL, title)
93+
94+-- | Inline elements.
95+data Inline
96+ = Str String -- ^ Text (string)
97+ | Emph [Inline] -- ^ Emphasized text (list of inlines)
98+ | Strong [Inline] -- ^ Strongly emphasized text (list of inlines)
99+ | Strikeout [Inline] -- ^ Strikeout text (list of inlines)
100+ | Superscript [Inline] -- ^ Superscripted text (list of inlines)
101+ | Subscript [Inline] -- ^ Subscripted text (list of inlines)
102+ | Quoted QuoteType [Inline] -- ^ Quoted text (list of inlines)
103+ | Code String -- ^ Inline code (literal)
104+ | Space -- ^ Inter-word space
105+ | EmDash -- ^ Em dash
106+ | EnDash -- ^ En dash
107+ | Apostrophe -- ^ Apostrophe
108+ | Ellipses -- ^ Ellipses
109+ | LineBreak -- ^ Hard line break
110+ | TeX String -- ^ LaTeX code (literal)
111+ | HtmlInline String -- ^ HTML code (literal)
112+ | Link [Inline] Target -- ^ Hyperlink: text (list of inlines), target
113+ | Image [Inline] Target -- ^ Image: alt text (list of inlines), target
114+ -- and target
115+ | Note [Block] -- ^ Footnote or endnote
116+ deriving (Show, Eq, Read)
src/Text/Pandoc/Readers/HTML.hsnew file+496
@@ -0,0 +1,496 @@
1+{-
2+Copyright (C) 2006-7 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.Readers.HTML
21+ Copyright : Copyright (C) 2006-7 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 HTML to 'Pandoc' document.
29+-}
30+module Text.Pandoc.Readers.HTML (
31+ readHtml,
32+ rawHtmlInline,
33+ rawHtmlBlock,
34+ anyHtmlBlockTag,
35+ anyHtmlInlineTag,
36+ anyHtmlTag,
37+ anyHtmlEndTag,
38+ htmlEndTag,
39+ extractTagType,
40+ htmlBlockElement
41+ ) where
42+
43+import Text.ParserCombinators.Parsec
44+import Text.Pandoc.Definition
45+import Text.Pandoc.Shared
46+import Text.Pandoc.CharacterReferences ( characterReference,
47+ decodeCharacterReferences )
48+import Data.Maybe ( fromMaybe )
49+import Data.List ( takeWhile, dropWhile, isPrefixOf, isSuffixOf )
50+import Data.Char ( toUpper, toLower, isAlphaNum )
51+
52+-- | Convert HTML-formatted string to 'Pandoc' document.
53+readHtml :: ParserState -- ^ Parser state
54+ -> String -- ^ String to parse
55+ -> Pandoc
56+readHtml = readWith parseHtml
57+
58+--
59+-- Constants
60+--
61+
62+eitherBlockOrInline = ["applet", "button", "del", "iframe", "ins",
63+ "map", "area", "object", "script"]
64+
65+inlineHtmlTags = ["a", "abbr", "acronym", "b", "basefont", "bdo", "big",
66+ "br", "cite", "code", "dfn", "em", "font", "i", "img",
67+ "input", "kbd", "label", "q", "s", "samp", "select",
68+ "small", "span", "strike", "strong", "sub", "sup",
69+ "textarea", "tt", "u", "var"] ++ eitherBlockOrInline
70+
71+blockHtmlTags = ["address", "blockquote", "center", "dir", "div",
72+ "dl", "fieldset", "form", "h1", "h2", "h3", "h4",
73+ "h5", "h6", "hr", "isindex", "menu", "noframes",
74+ "noscript", "ol", "p", "pre", "table", "ul", "dd",
75+ "dt", "frameset", "li", "tbody", "td", "tfoot",
76+ "th", "thead", "tr"] ++ eitherBlockOrInline
77+
78+--
79+-- HTML utility functions
80+--
81+
82+-- | Read blocks until end tag.
83+blocksTilEnd tag = do
84+ blocks <- manyTill (block >>~ spaces) (htmlEndTag tag)
85+ return $ filter (/= Null) blocks
86+
87+-- | Read inlines until end tag.
88+inlinesTilEnd tag = manyTill inline (htmlEndTag tag)
89+
90+-- | Parse blocks between open and close tag.
91+blocksIn tag = try $ htmlTag tag >> spaces >> blocksTilEnd tag
92+
93+-- | Parse inlines between open and close tag.
94+inlinesIn tag = try $ htmlTag tag >> spaces >> inlinesTilEnd tag
95+
96+-- | Extract type from a tag: e.g. @br@ from @\<br\>@
97+extractTagType :: String -> String
98+extractTagType ('<':rest) =
99+ let isSpaceOrSlash c = c `elem` "/ \n\t" in
100+ map toLower $ takeWhile isAlphaNum $ dropWhile isSpaceOrSlash rest
101+extractTagType _ = ""
102+
103+-- | Parse any HTML tag (opening or self-closing) and return text of tag
104+anyHtmlTag = try $ do
105+ char '<'
106+ spaces
107+ tag <- many1 alphaNum
108+ attribs <- many htmlAttribute
109+ spaces
110+ ender <- option "" (string "/")
111+ let ender' = if null ender then "" else " /"
112+ spaces
113+ char '>'
114+ return $ "<" ++ tag ++
115+ concatMap (\(_, _, raw) -> (' ':raw)) attribs ++ ender' ++ ">"
116+
117+anyHtmlEndTag = try $ do
118+ char '<'
119+ spaces
120+ char '/'
121+ spaces
122+ tagType <- many1 alphaNum
123+ spaces
124+ char '>'
125+ return $ "</" ++ tagType ++ ">"
126+
127+htmlTag :: String -> GenParser Char st (String, [(String, String)])
128+htmlTag tag = try $ do
129+ char '<'
130+ spaces
131+ stringAnyCase tag
132+ attribs <- many htmlAttribute
133+ spaces
134+ optional (string "/")
135+ spaces
136+ char '>'
137+ return (tag, (map (\(name, content, raw) -> (name, content)) attribs))
138+
139+-- parses a quoted html attribute value
140+quoted quoteChar = do
141+ result <- between (char quoteChar) (char quoteChar)
142+ (many (noneOf [quoteChar]))
143+ return (result, [quoteChar])
144+
145+htmlAttribute = htmlRegularAttribute <|> htmlMinimizedAttribute
146+
147+-- minimized boolean attribute
148+htmlMinimizedAttribute = try $ do
149+ many1 space
150+ name <- many1 (choice [letter, oneOf ".-_:"])
151+ return (name, name, name)
152+
153+htmlRegularAttribute = try $ do
154+ many1 space
155+ name <- many1 (choice [letter, oneOf ".-_:"])
156+ spaces
157+ char '='
158+ spaces
159+ (content, quoteStr) <- choice [ (quoted '\''),
160+ (quoted '"'),
161+ (do
162+ a <- many (alphaNum <|> (oneOf "-._:"))
163+ return (a,"")) ]
164+ return (name, content,
165+ (name ++ "=" ++ quoteStr ++ content ++ quoteStr))
166+
167+-- | Parse an end tag of type 'tag'
168+htmlEndTag tag = try $ do
169+ char '<'
170+ spaces
171+ char '/'
172+ spaces
173+ stringAnyCase tag
174+ spaces
175+ char '>'
176+ return $ "</" ++ tag ++ ">"
177+
178+-- | Returns @True@ if the tag is (or can be) an inline tag.
179+isInline tag = (extractTagType tag) `elem` inlineHtmlTags
180+
181+-- | Returns @True@ if the tag is (or can be) a block tag.
182+isBlock tag = (extractTagType tag) `elem` blockHtmlTags
183+
184+anyHtmlBlockTag = try $ do
185+ tag <- anyHtmlTag <|> anyHtmlEndTag
186+ if isBlock tag then return tag else fail "inline tag"
187+
188+anyHtmlInlineTag = try $ do
189+ tag <- anyHtmlTag <|> anyHtmlEndTag
190+ if isInline tag then return tag else fail "not an inline tag"
191+
192+-- | Parses material between script tags.
193+-- Scripts must be treated differently, because they can contain '<>' etc.
194+htmlScript = try $ do
195+ open <- string "<script"
196+ rest <- manyTill anyChar (htmlEndTag "script")
197+ return $ open ++ rest ++ "</script>"
198+
199+htmlBlockElement = choice [ htmlScript, htmlComment, xmlDec, definition ]
200+
201+rawHtmlBlock = try $ do
202+ notFollowedBy' (htmlTag "/body" <|> htmlTag "/html")
203+ body <- htmlBlockElement <|> anyHtmlTag <|> anyHtmlEndTag
204+ sp <- many space
205+ state <- getState
206+ if stateParseRaw state then return (RawHtml (body ++ sp)) else return Null
207+
208+-- | Parses an HTML comment.
209+htmlComment = try $ do
210+ string "<!--"
211+ comment <- manyTill anyChar (try (string "-->"))
212+ return $ "<!--" ++ comment ++ "-->"
213+
214+--
215+-- parsing documents
216+--
217+
218+xmlDec = try $ do
219+ string "<?"
220+ rest <- manyTill anyChar (char '>')
221+ return $ "<?" ++ rest ++ ">"
222+
223+definition = try $ do
224+ string "<!"
225+ rest <- manyTill anyChar (char '>')
226+ return $ "<!" ++ rest ++ ">"
227+
228+nonTitleNonHead = try $ notFollowedBy' (htmlTag "title" <|> htmlTag "/head") >>
229+ ((rawHtmlBlock >> return ' ') <|> anyChar)
230+
231+parseTitle = try $ do
232+ (tag, _) <- htmlTag "title"
233+ contents <- inlinesTilEnd tag
234+ spaces
235+ return contents
236+
237+-- parse header and return meta-information (for now, just title)
238+parseHead = try $ do
239+ htmlTag "head"
240+ spaces
241+ skipMany nonTitleNonHead
242+ contents <- option [] parseTitle
243+ skipMany nonTitleNonHead
244+ htmlTag "/head"
245+ return (contents, [], "")
246+
247+skipHtmlTag tag = optional (htmlTag tag)
248+
249+-- h1 class="title" representation of title in body
250+bodyTitle = try $ do
251+ (tag, attribs) <- htmlTag "h1"
252+ cl <- case (extractAttribute "class" attribs) of
253+ Just "title" -> return ""
254+ otherwise -> fail "not title"
255+ inlinesTilEnd "h1"
256+
257+parseHtml = do
258+ sepEndBy (choice [xmlDec, definition, htmlComment]) spaces
259+ skipHtmlTag "html"
260+ spaces
261+ (title, authors, date) <- option ([], [], "") parseHead
262+ spaces
263+ skipHtmlTag "body"
264+ spaces
265+ optional bodyTitle -- skip title in body, because it's represented in meta
266+ blocks <- parseBlocks
267+ spaces
268+ optional (htmlEndTag "body")
269+ spaces
270+ optional (htmlEndTag "html" >> many anyChar) -- ignore anything after </html>
271+ eof
272+ return $ Pandoc (Meta title authors date) blocks
273+
274+--
275+-- parsing blocks
276+--
277+
278+parseBlocks = spaces >> sepEndBy block spaces >>= (return . filter (/= Null))
279+
280+block = choice [ codeBlock
281+ , header
282+ , hrule
283+ , list
284+ , blockQuote
285+ , para
286+ , plain
287+ , rawHtmlBlock ] <?> "block"
288+
289+--
290+-- header blocks
291+--
292+
293+header = choice (map headerLevel (enumFromTo 1 5)) <?> "header"
294+
295+headerLevel n = try $ do
296+ let level = "h" ++ show n
297+ (tag, attribs) <- htmlTag level
298+ contents <- inlinesTilEnd level
299+ return $ Header n (normalizeSpaces contents)
300+
301+--
302+-- hrule block
303+--
304+
305+hrule = try $ do
306+ (tag, attribs) <- htmlTag "hr"
307+ state <- getState
308+ if not (null attribs) && stateParseRaw state
309+ then unexpected "attributes in hr" -- parse as raw in this case
310+ else return HorizontalRule
311+
312+--
313+-- code blocks
314+--
315+
316+-- Note: HTML tags in code blocks (e.g. for syntax highlighting) are
317+-- skipped, because they are not portable to output formats other than HTML.
318+codeBlock = try $ do
319+ htmlTag "pre"
320+ result <- manyTill
321+ (many1 (satisfy (/= '<')) <|>
322+ ((anyHtmlTag <|> anyHtmlEndTag) >> return ""))
323+ (htmlEndTag "pre")
324+ let result' = concat result
325+ -- drop leading newline if any
326+ let result'' = if "\n" `isPrefixOf` result'
327+ then drop 1 result'
328+ else result'
329+ -- drop trailing newline if any
330+ let result''' = if "\n" `isSuffixOf` result''
331+ then init result''
332+ else result''
333+ return $ CodeBlock $ decodeCharacterReferences result'''
334+
335+--
336+-- block quotes
337+--
338+
339+blockQuote = try $ htmlTag "blockquote" >> spaces >>
340+ blocksTilEnd "blockquote" >>= (return . BlockQuote)
341+
342+--
343+-- list blocks
344+--
345+
346+list = choice [ bulletList, orderedList, definitionList ] <?> "list"
347+
348+orderedList = try $ do
349+ (_, attribs) <- htmlTag "ol"
350+ (start, style) <- option (1, DefaultStyle) $
351+ do failIfStrict
352+ let sta = fromMaybe "1" $
353+ lookup "start" attribs
354+ let sty = fromMaybe (fromMaybe "" $
355+ lookup "style" attribs) $
356+ lookup "class" attribs
357+ let sty' = case sty of
358+ "lower-roman" -> LowerRoman
359+ "upper-roman" -> UpperRoman
360+ "lower-alpha" -> LowerAlpha
361+ "upper-alpha" -> UpperAlpha
362+ "decimal" -> Decimal
363+ _ -> DefaultStyle
364+ return (read sta, sty')
365+ spaces
366+ items <- sepEndBy1 (blocksIn "li") spaces
367+ htmlEndTag "ol"
368+ return $ OrderedList (start, style, DefaultDelim) items
369+
370+bulletList = try $ do
371+ htmlTag "ul"
372+ spaces
373+ items <- sepEndBy1 (blocksIn "li") spaces
374+ htmlEndTag "ul"
375+ return $ BulletList items
376+
377+definitionList = try $ do
378+ failIfStrict -- def lists not part of standard markdown
379+ tag <- htmlTag "dl"
380+ spaces
381+ items <- sepEndBy1 definitionListItem spaces
382+ htmlEndTag "dl"
383+ return $ DefinitionList items
384+
385+definitionListItem = try $ do
386+ terms <- sepEndBy1 (inlinesIn "dt") spaces
387+ defs <- sepEndBy1 (blocksIn "dd") spaces
388+ let term = joinWithSep [LineBreak] terms
389+ return (term, concat defs)
390+
391+--
392+-- paragraph block
393+--
394+
395+para = try $ htmlTag "p" >> inlinesTilEnd "p" >>=
396+ return . Para . normalizeSpaces
397+
398+--
399+-- plain block
400+--
401+
402+plain = many1 inline >>= return . Plain . normalizeSpaces
403+
404+--
405+-- inline
406+--
407+
408+inline = choice [ charRef
409+ , strong
410+ , emph
411+ , superscript
412+ , subscript
413+ , strikeout
414+ , spanStrikeout
415+ , code
416+ , str
417+ , linebreak
418+ , whitespace
419+ , link
420+ , image
421+ , rawHtmlInline
422+ ] <?> "inline"
423+
424+code = try $ do
425+ htmlTag "code"
426+ result <- manyTill anyChar (htmlEndTag "code")
427+ -- remove internal line breaks, leading and trailing space,
428+ -- and decode character references
429+ return $ Code $ decodeCharacterReferences $ removeLeadingTrailingSpace $
430+ joinWithSep " " $ lines result
431+
432+rawHtmlInline = do
433+ result <- htmlScript <|> htmlComment <|> anyHtmlInlineTag
434+ state <- getState
435+ if stateParseRaw state then return (HtmlInline result) else return (Str "")
436+
437+betweenTags tag = try $ htmlTag tag >> inlinesTilEnd tag >>=
438+ return . normalizeSpaces
439+
440+emph = (betweenTags "em" <|> betweenTags "it") >>= return . Emph
441+
442+strong = (betweenTags "b" <|> betweenTags "strong") >>= return . Strong
443+
444+superscript = failIfStrict >> betweenTags "sup" >>= return . Superscript
445+
446+subscript = failIfStrict >> betweenTags "sub" >>= return . Subscript
447+
448+strikeout = failIfStrict >> (betweenTags "s" <|> betweenTags "strike") >>=
449+ return . Strikeout
450+
451+spanStrikeout = try $ do
452+ failIfStrict -- strict markdown has no strikeout, so treat as raw HTML
453+ (tag, attributes) <- htmlTag "span"
454+ result <- case (extractAttribute "class" attributes) of
455+ Just "strikeout" -> inlinesTilEnd "span"
456+ _ -> fail "not a strikeout"
457+ return $ Strikeout result
458+
459+whitespace = many1 space >> return Space
460+
461+-- hard line break
462+linebreak = htmlTag "br" >> optional newline >> return LineBreak
463+
464+str = many1 (noneOf "<& \t\n") >>= return . Str
465+
466+--
467+-- links and images
468+--
469+
470+-- extract contents of attribute (attribute names are case-insensitive)
471+extractAttribute name [] = Nothing
472+extractAttribute name ((attrName, contents):rest) =
473+ let name' = map toLower name
474+ attrName' = map toLower attrName
475+ in if attrName' == name'
476+ then Just (decodeCharacterReferences contents)
477+ else extractAttribute name rest
478+
479+link = try $ do
480+ (tag, attributes) <- htmlTag "a"
481+ url <- case (extractAttribute "href" attributes) of
482+ Just url -> return url
483+ Nothing -> fail "no href"
484+ let title = fromMaybe "" $ extractAttribute "title" attributes
485+ label <- inlinesTilEnd "a"
486+ return $ Link (normalizeSpaces label) (url, title)
487+
488+image = try $ do
489+ (tag, attributes) <- htmlTag "img"
490+ url <- case (extractAttribute "src" attributes) of
491+ Just url -> return url
492+ Nothing -> fail "no src"
493+ let title = fromMaybe "" $ extractAttribute "title" attributes
494+ let alt = fromMaybe "" (extractAttribute "alt" attributes)
495+ return $ Image [Str alt] (url, title)
496+
src/Text/Pandoc/Readers/LaTeX.hsnew file+651
@@ -0,0 +1,651 @@
1+{-
2+Copyright (C) 2006-7 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.Readers.LaTeX
21+ Copyright : Copyright (C) 2006-7 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 LaTeX to 'Pandoc' document.
29+-}
30+module Text.Pandoc.Readers.LaTeX (
31+ readLaTeX,
32+ rawLaTeXInline,
33+ rawLaTeXEnvironment
34+ ) where
35+
36+import Text.ParserCombinators.Parsec
37+import Text.Pandoc.Definition
38+import Text.Pandoc.Shared
39+import Data.Maybe ( fromMaybe )
40+import Data.Char ( chr )
41+import Data.List ( isPrefixOf, isSuffixOf )
42+
43+-- | Parse LaTeX from string and return 'Pandoc' document.
44+readLaTeX :: ParserState -- ^ Parser state, including options for parser
45+ -> String -- ^ String to parse
46+ -> Pandoc
47+readLaTeX = readWith parseLaTeX
48+
49+-- characters with special meaning
50+specialChars = "\\`$%^&_~#{}\n \t|<>'\"-"
51+
52+--
53+-- utility functions
54+--
55+
56+-- | Returns text between brackets and its matching pair.
57+bracketedText openB closeB = do
58+ result <- charsInBalanced' openB closeB
59+ return $ [openB] ++ result ++ [closeB]
60+
61+-- | Returns an option or argument of a LaTeX command.
62+optOrArg = bracketedText '{' '}' <|> bracketedText '[' ']'
63+
64+-- | True if the string begins with '{'.
65+isArg ('{':rest) = True
66+isArg other = False
67+
68+-- | Returns list of options and arguments of a LaTeX command.
69+commandArgs = many optOrArg
70+
71+-- | Parses LaTeX command, returns (name, star, list of options or arguments).
72+command = do
73+ char '\\'
74+ name <- many1 letter
75+ star <- option "" (string "*") -- some commands have starred versions
76+ args <- commandArgs
77+ return (name, star, args)
78+
79+begin name = try $ do
80+ string $ "\\begin{" ++ name ++ "}"
81+ optional commandArgs
82+ spaces
83+ return name
84+
85+end name = try $ do
86+ string $ "\\end{" ++ name ++ "}"
87+ spaces
88+ return name
89+
90+-- | Returns a list of block elements containing the contents of an
91+-- environment.
92+environment name = try $ begin name >> spaces >> manyTill block (end name)
93+
94+anyEnvironment = try $ do
95+ string "\\begin{"
96+ name <- many letter
97+ star <- option "" (string "*") -- some environments have starred variants
98+ char '}'
99+ optional commandArgs
100+ spaces
101+ contents <- manyTill block (end (name ++ star))
102+ return $ BlockQuote contents
103+
104+--
105+-- parsing documents
106+--
107+
108+-- | Process LaTeX preamble, extracting metadata.
109+processLaTeXPreamble = try $ manyTill
110+ (choice [bibliographic, comment, unknownCommand, nullBlock])
111+ (try (string "\\begin{document}")) >>
112+ spaces
113+
114+-- | Parse LaTeX and return 'Pandoc'.
115+parseLaTeX = do
116+ optional processLaTeXPreamble -- preamble might not be present (fragment)
117+ spaces
118+ blocks <- parseBlocks
119+ spaces
120+ optional $ try (string "\\end{document}" >> many anyChar)
121+ -- might not be present (fragment)
122+ spaces
123+ eof
124+ state <- getState
125+ let blocks' = filter (/= Null) blocks
126+ let title' = stateTitle state
127+ let authors' = stateAuthors state
128+ let date' = stateDate state
129+ return $ Pandoc (Meta title' authors' date') blocks'
130+
131+--
132+-- parsing blocks
133+--
134+
135+parseBlocks = spaces >> many block
136+
137+block = choice [ hrule
138+ , codeBlock
139+ , header
140+ , list
141+ , blockQuote
142+ , mathBlock
143+ , comment
144+ , bibliographic
145+ , para
146+ , specialEnvironment
147+ , itemBlock
148+ , unknownEnvironment
149+ , unknownCommand ] <?> "block"
150+
151+--
152+-- header blocks
153+--
154+
155+header = try $ do
156+ char '\\'
157+ subs <- many (try (string "sub"))
158+ string "section"
159+ optional (char '*')
160+ char '{'
161+ title <- manyTill inline (char '}')
162+ spaces
163+ return $ Header (length subs + 1) (normalizeSpaces title)
164+
165+--
166+-- hrule block
167+--
168+
169+hrule = oneOfStrings [ "\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n\n",
170+ "\\newpage" ] >> spaces >> return HorizontalRule
171+
172+--
173+-- code blocks
174+--
175+
176+codeBlock = codeBlock1 <|> codeBlock2
177+
178+codeBlock1 = try $ do
179+ string "\\begin{verbatim}" -- don't use begin function because it
180+ -- gobbles whitespace
181+ optional blanklines -- we want to gobble blank lines, but not
182+ -- leading space
183+ contents <- manyTill anyChar (try (string "\\end{verbatim}"))
184+ spaces
185+ return $ CodeBlock (stripTrailingNewlines contents)
186+
187+codeBlock2 = try $ do
188+ string "\\begin{Verbatim}" -- used by fancyvrb package
189+ option "" blanklines
190+ contents <- manyTill anyChar (try (string "\\end{Verbatim}"))
191+ spaces
192+ return $ CodeBlock (stripTrailingNewlines contents)
193+
194+--
195+-- block quotes
196+--
197+
198+blockQuote = (environment "quote" <|> environment "quotation") >>~ spaces >>=
199+ return . BlockQuote
200+
201+--
202+-- math block
203+--
204+
205+mathBlock = mathBlockWith (begin "equation") (end "equation") <|>
206+ mathBlockWith (begin "displaymath") (end "displaymath") <|>
207+ mathBlockWith (string "\\[") (string "\\]") <?> "math block"
208+
209+mathBlockWith start end = try $ do
210+ start
211+ spaces
212+ result <- manyTill anyChar end
213+ spaces
214+ return $ BlockQuote [Para [TeX ("$" ++ result ++ "$")]]
215+
216+--
217+-- list blocks
218+--
219+
220+list = bulletList <|> orderedList <|> definitionList <?> "list"
221+
222+listItem = try $ do
223+ ("item", _, args) <- command
224+ spaces
225+ state <- getState
226+ let oldParserContext = stateParserContext state
227+ updateState (\state -> state {stateParserContext = ListItemState})
228+ blocks <- many block
229+ updateState (\state -> state {stateParserContext = oldParserContext})
230+ opt <- case args of
231+ ([x]) | "[" `isPrefixOf` x && "]" `isSuffixOf` x ->
232+ parseFromString (many inline) $ tail $ init x
233+ _ -> return []
234+ return (opt, blocks)
235+
236+orderedList = try $ do
237+ string "\\begin{enumerate}"
238+ (_, style, delim) <- option (1, DefaultStyle, DefaultDelim) $
239+ try $ do failIfStrict
240+ char '['
241+ res <- anyOrderedListMarker
242+ char ']'
243+ return res
244+ spaces
245+ option "" $ try $ do string "\\setlength{\\itemindent}"
246+ char '{'
247+ manyTill anyChar (char '}')
248+ spaces
249+ start <- option 1 $ try $ do failIfStrict
250+ string "\\setcounter{enum"
251+ many1 (oneOf "iv")
252+ string "}{"
253+ num <- many1 digit
254+ char '}'
255+ spaces
256+ return $ (read num) + 1
257+ items <- many listItem
258+ end "enumerate"
259+ spaces
260+ return $ OrderedList (start, style, delim) $ map snd items
261+
262+bulletList = try $ do
263+ begin "itemize"
264+ spaces
265+ items <- many listItem
266+ end "itemize"
267+ spaces
268+ return (BulletList $ map snd items)
269+
270+definitionList = try $ do
271+ begin "description"
272+ spaces
273+ items <- many listItem
274+ end "description"
275+ spaces
276+ return (DefinitionList items)
277+
278+--
279+-- paragraph block
280+--
281+
282+para = many1 inline >>~ spaces >>= return . Para . normalizeSpaces
283+
284+--
285+-- title authors date
286+--
287+
288+bibliographic = choice [ maketitle, title, authors, date ]
289+
290+maketitle = try (string "\\maketitle") >> spaces >> return Null
291+
292+title = try $ do
293+ string "\\title{"
294+ tit <- manyTill inline (char '}')
295+ spaces
296+ updateState (\state -> state { stateTitle = tit })
297+ return Null
298+
299+authors = try $ do
300+ string "\\author{"
301+ authors <- manyTill anyChar (char '}')
302+ spaces
303+ let authors' = map removeLeadingTrailingSpace $ lines $
304+ substitute "\\\\" "\n" authors
305+ updateState (\state -> state { stateAuthors = authors' })
306+ return Null
307+
308+date = try $ do
309+ string "\\date{"
310+ date' <- manyTill anyChar (char '}')
311+ spaces
312+ updateState (\state -> state { stateDate = date' })
313+ return Null
314+
315+--
316+-- item block
317+-- for use in unknown environments that aren't being parsed as raw latex
318+--
319+
320+-- this forces items to be parsed in different blocks
321+itemBlock = try $ do
322+ ("item", _, args) <- command
323+ state <- getState
324+ if (stateParserContext state == ListItemState)
325+ then fail "item should be handled by list block"
326+ else if null args
327+ then return Null
328+ else return $ Plain [Str (stripFirstAndLast (head args))]
329+
330+--
331+-- raw LaTeX
332+--
333+
334+specialEnvironment = do -- these are always parsed as raw
335+ lookAhead (choice (map (\name -> begin name) ["tabular", "figure",
336+ "tabbing", "eqnarry", "picture", "table", "verse", "theorem"]))
337+ rawLaTeXEnvironment
338+
339+-- | Parse any LaTeX environment and return a Para block containing
340+-- the whole literal environment as raw TeX.
341+rawLaTeXEnvironment :: GenParser Char st Block
342+rawLaTeXEnvironment = try $ do
343+ string "\\begin{"
344+ name <- many1 letter
345+ star <- option "" (string "*") -- for starred variants
346+ let name' = name ++ star
347+ char '}'
348+ args <- option [] commandArgs
349+ let argStr = concat args
350+ contents <- manyTill (choice [ (many1 (noneOf "\\")),
351+ (do
352+ (Para [TeX str]) <- rawLaTeXEnvironment
353+ return str),
354+ string "\\" ])
355+ (end name')
356+ spaces
357+ return $ Para [TeX $ "\\begin{" ++ name' ++ "}" ++ argStr ++
358+ concat contents ++ "\\end{" ++ name' ++ "}"]
359+
360+unknownEnvironment = try $ do
361+ state <- getState
362+ result <- if stateParseRaw state -- check whether we should include raw TeX
363+ then rawLaTeXEnvironment -- if so, get whole raw environment
364+ else anyEnvironment -- otherwise just the contents
365+ return result
366+
367+unknownCommand = try $ do
368+ notFollowedBy' $ choice $ map end ["itemize", "enumerate", "description",
369+ "document"]
370+ (name, star, args) <- command
371+ spaces
372+ let argStr = concat args
373+ state <- getState
374+ if name == "item" && (stateParserContext state) == ListItemState
375+ then fail "should not be parsed as raw"
376+ else string ""
377+ if stateParseRaw state
378+ then return $ Plain [TeX ("\\" ++ name ++ star ++ argStr)]
379+ else return $ Plain [Str (joinWithSep " " args)]
380+
381+-- latex comment
382+comment = try $ char '%' >> manyTill anyChar newline >> spaces >> return Null
383+
384+--
385+-- inline
386+--
387+
388+inline = choice [ str
389+ , endline
390+ , whitespace
391+ , quoted
392+ , apostrophe
393+ , spacer
394+ , strong
395+ , math
396+ , ellipses
397+ , emDash
398+ , enDash
399+ , hyphen
400+ , emph
401+ , strikeout
402+ , superscript
403+ , subscript
404+ , ref
405+ , lab
406+ , code
407+ , url
408+ , link
409+ , image
410+ , footnote
411+ , linebreak
412+ , accentedChar
413+ , specialChar
414+ , rawLaTeXInline
415+ , escapedChar
416+ , unescapedChar
417+ ] <?> "inline"
418+
419+accentedChar = normalAccentedChar <|> specialAccentedChar
420+
421+normalAccentedChar = try $ do
422+ char '\\'
423+ accent <- oneOf "'`^\"~"
424+ character <- (try $ char '{' >> letter >>~ char '}') <|> letter
425+ let table = fromMaybe [] $ lookup character accentTable
426+ let result = case lookup accent table of
427+ Just num -> chr num
428+ Nothing -> '?'
429+ return $ Str [result]
430+
431+-- an association list of letters and association list of accents
432+-- and decimal character numbers.
433+accentTable =
434+ [ ('A', [('`', 192), ('\'', 193), ('^', 194), ('~', 195), ('"', 196)]),
435+ ('E', [('`', 200), ('\'', 201), ('^', 202), ('"', 203)]),
436+ ('I', [('`', 204), ('\'', 205), ('^', 206), ('"', 207)]),
437+ ('N', [('~', 209)]),
438+ ('O', [('`', 210), ('\'', 211), ('^', 212), ('~', 213), ('"', 214)]),
439+ ('U', [('`', 217), ('\'', 218), ('^', 219), ('"', 220)]),
440+ ('a', [('`', 224), ('\'', 225), ('^', 227), ('"', 228)]),
441+ ('e', [('`', 232), ('\'', 233), ('^', 234), ('"', 235)]),
442+ ('i', [('`', 236), ('\'', 237), ('^', 238), ('"', 239)]),
443+ ('n', [('~', 241)]),
444+ ('o', [('`', 242), ('\'', 243), ('^', 244), ('~', 245), ('"', 246)]),
445+ ('u', [('`', 249), ('\'', 250), ('^', 251), ('"', 252)]) ]
446+
447+specialAccentedChar = choice [ ccedil, aring, iuml, szlig, aelig,
448+ oslash, pound, euro, copyright, sect ]
449+
450+ccedil = try $ do
451+ char '\\'
452+ letter <- oneOfStrings ["cc", "cC"]
453+ let num = if letter == "cc" then 231 else 199
454+ return $ Str [chr num]
455+
456+aring = try $ do
457+ char '\\'
458+ letter <- oneOfStrings ["aa", "AA"]
459+ let num = if letter == "aa" then 229 else 197
460+ return $ Str [chr num]
461+
462+iuml = try (string "\\\"") >> oneOfStrings ["\\i", "{\\i}"] >>
463+ return (Str [chr 239])
464+
465+icirc = try (string "\\^") >> oneOfStrings ["\\i", "{\\i}"] >>
466+ return (Str [chr 238])
467+
468+szlig = try (string "\\ss") >> return (Str [chr 223])
469+
470+oslash = try $ do
471+ char '\\'
472+ letter <- choice [char 'o', char 'O']
473+ let num = if letter == 'o' then 248 else 216
474+ return $ Str [chr num]
475+
476+aelig = try $ do
477+ char '\\'
478+ letter <- oneOfStrings ["ae", "AE"]
479+ let num = if letter == "ae" then 230 else 198
480+ return $ Str [chr num]
481+
482+pound = try (string "\\pounds") >> return (Str [chr 163])
483+
484+euro = try (string "\\euro") >> return (Str [chr 8364])
485+
486+copyright = try (string "\\copyright") >> return (Str [chr 169])
487+
488+sect = try (string "\\S") >> return (Str [chr 167])
489+
490+escapedChar = do
491+ result <- escaped (oneOf " $%&_#{}\n")
492+ return $ if result == Str "\n" then Str " " else result
493+
494+-- ignore standalone, nonescaped special characters
495+unescapedChar = oneOf "`$^&_#{}|<>" >> return (Str "")
496+
497+specialChar = choice [ backslash, tilde, caret, bar, lt, gt, doubleQuote ]
498+
499+backslash = try (string "\\textbackslash") >> return (Str "\\")
500+
501+tilde = try (string "\\ensuremath{\\sim}") >> return (Str "~")
502+
503+caret = try (string "\\^{}") >> return (Str "^")
504+
505+bar = try (string "\\textbar") >> return (Str "\\")
506+
507+lt = try (string "\\textless") >> return (Str "<")
508+
509+gt = try (string "\\textgreater") >> return (Str ">")
510+
511+doubleQuote = char '"' >> return (Str "\"")
512+
513+code = code1 <|> code2
514+
515+code1 = try $ do
516+ string "\\verb"
517+ marker <- anyChar
518+ result <- manyTill anyChar (char marker)
519+ return $ Code $ removeLeadingTrailingSpace result
520+
521+code2 = try $ do
522+ string "\\texttt{"
523+ result <- manyTill (noneOf "\\\n~$%^&{}") (char '}')
524+ return $ Code result
525+
526+emph = try $ oneOfStrings [ "\\emph{", "\\textit{" ] >>
527+ manyTill inline (char '}') >>= return . Emph
528+
529+strikeout = try $ string "\\sout{" >> manyTill inline (char '}') >>=
530+ return . Strikeout
531+
532+superscript = try $ string "\\textsuperscript{" >>
533+ manyTill inline (char '}') >>= return . Superscript
534+
535+-- note: \textsubscript isn't a standard latex command, but we use
536+-- a defined version in pandoc.
537+subscript = try $ string "\\textsubscript{" >> manyTill inline (char '}') >>=
538+ return . Subscript
539+
540+apostrophe = char '\'' >> return Apostrophe
541+
542+quoted = doubleQuoted <|> singleQuoted
543+
544+singleQuoted = enclosed singleQuoteStart singleQuoteEnd inline >>=
545+ return . Quoted SingleQuote . normalizeSpaces
546+
547+doubleQuoted = enclosed doubleQuoteStart doubleQuoteEnd inline >>=
548+ return . Quoted DoubleQuote . normalizeSpaces
549+
550+singleQuoteStart = char '`'
551+
552+singleQuoteEnd = try $ char '\'' >> notFollowedBy alphaNum
553+
554+doubleQuoteStart = string "``"
555+
556+doubleQuoteEnd = try $ string "''"
557+
558+ellipses = try $ string "\\ldots" >> optional (try (string "{}")) >>
559+ return Ellipses
560+
561+enDash = try (string "--") >> return EnDash
562+
563+emDash = try (string "---") >> return EmDash
564+
565+hyphen = char '-' >> return (Str "-")
566+
567+lab = try $ do
568+ string "\\label{"
569+ result <- manyTill anyChar (char '}')
570+ return $ Str $ "(" ++ result ++ ")"
571+
572+ref = try (string "\\ref{") >> manyTill anyChar (char '}') >>= return . Str
573+
574+strong = try (string "\\textbf{") >> manyTill inline (char '}') >>=
575+ return . Strong
576+
577+whitespace = many1 (oneOf "~ \t") >> return Space
578+
579+-- hard line break
580+linebreak = try (string "\\\\") >> return LineBreak
581+
582+spacer = try (string "\\,") >> return (Str "")
583+
584+str = many1 (noneOf specialChars) >>= return . Str
585+
586+-- endline internal to paragraph
587+endline = try $ newline >> notFollowedBy blankline >> return Space
588+
589+-- math
590+math = math1 <|> math2 <?> "math"
591+
592+math1 = try $ do
593+ char '$'
594+ result <- many (noneOf "$")
595+ char '$'
596+ return $ TeX ("$" ++ result ++ "$")
597+
598+math2 = try $ do
599+ string "\\("
600+ result <- many (noneOf "$")
601+ string "\\)"
602+ return $ TeX ("$" ++ result ++ "$")
603+
604+--
605+-- links and images
606+--
607+
608+url = try $ do
609+ string "\\url"
610+ url <- charsInBalanced '{' '}'
611+ return $ Link [Code url] (url, "")
612+
613+link = try $ do
614+ string "\\href{"
615+ url <- manyTill anyChar (char '}')
616+ char '{'
617+ label <- manyTill inline (char '}')
618+ return $ Link (normalizeSpaces label) (url, "")
619+
620+image = try $ do
621+ ("includegraphics", _, args) <- command
622+ let args' = filter isArg args -- filter out options
623+ let src = if null args' then
624+ ("", "")
625+ else
626+ (stripFirstAndLast (head args'), "")
627+ return $ Image [Str "image"] src
628+
629+footnote = try $ do
630+ (name, _, (contents:[])) <- command
631+ if ((name == "footnote") || (name == "thanks"))
632+ then string ""
633+ else fail "not a footnote or thanks command"
634+ let contents' = stripFirstAndLast contents
635+ -- parse the extracted block, which may contain various block elements:
636+ rest <- getInput
637+ setInput $ contents'
638+ blocks <- parseBlocks
639+ setInput rest
640+ return $ Note blocks
641+
642+-- | Parse any LaTeX command and return it in a raw TeX inline element.
643+rawLaTeXInline :: GenParser Char ParserState Inline
644+rawLaTeXInline = try $ do
645+ (name, star, args) <- command
646+ state <- getState
647+ if ((name == "begin") || (name == "end") || (name == "item"))
648+ then fail "not an inline command"
649+ else string ""
650+ return $ TeX ("\\" ++ name ++ star ++ concat args)
651+
src/Text/Pandoc/Readers/Markdown.hsnew file+909
@@ -0,0 +1,909 @@
1+{-
2+Copyright (C) 2006-7 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.Readers.Markdown
21+ Copyright : Copyright (C) 2006-7 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 markdown-formatted plain text to 'Pandoc' document.
29+-}
30+module Text.Pandoc.Readers.Markdown (
31+ readMarkdown
32+ ) where
33+
34+import Data.List ( transpose, isPrefixOf, isSuffixOf, lookup, sortBy )
35+import Data.Ord ( comparing )
36+import Data.Char ( isAlphaNum )
37+import Network.URI ( isURI )
38+import Text.Pandoc.Definition
39+import Text.Pandoc.Shared
40+import Text.Pandoc.Readers.LaTeX ( rawLaTeXInline, rawLaTeXEnvironment )
41+import Text.Pandoc.Readers.HTML ( rawHtmlBlock, anyHtmlBlockTag,
42+ anyHtmlInlineTag, anyHtmlTag,
43+ anyHtmlEndTag, htmlEndTag, extractTagType,
44+ htmlBlockElement )
45+import Text.Pandoc.CharacterReferences ( decodeCharacterReferences )
46+import Text.ParserCombinators.Parsec
47+
48+-- | Read markdown from an input string and return a Pandoc document.
49+readMarkdown :: ParserState -> String -> Pandoc
50+readMarkdown state str = (readWith parseMarkdown) state (str ++ "\n\n")
51+
52+--
53+-- Constants and data structure definitions
54+--
55+
56+spaceChars = " \t"
57+bulletListMarkers = "*+-"
58+hruleChars = "*-_"
59+setextHChars = "=-"
60+
61+-- treat these as potentially non-text when parsing inline:
62+specialChars = "\\[]*_~`<>$!^-.&'\""
63+
64+--
65+-- auxiliary functions
66+--
67+
68+indentSpaces = try $ do
69+ state <- getState
70+ let tabStop = stateTabStop state
71+ try (count tabStop (char ' ')) <|>
72+ (many (char ' ') >> string "\t") <?> "indentation"
73+
74+nonindentSpaces = do
75+ state <- getState
76+ let tabStop = stateTabStop state
77+ sps <- many (char ' ')
78+ if length sps < tabStop
79+ then return sps
80+ else unexpected "indented line"
81+
82+-- | Fail unless we're at beginning of a line.
83+failUnlessBeginningOfLine = do
84+ pos <- getPosition
85+ if sourceColumn pos == 1 then return () else fail "not beginning of line"
86+
87+-- | Fail unless we're in "smart typography" mode.
88+failUnlessSmart = do
89+ state <- getState
90+ if stateSmart state then return () else fail "Smart typography feature"
91+
92+-- | Parse an inline Str element with a given content.
93+inlineString str = try $ do
94+ (Str res) <- inline
95+ if res == str then return res else fail $ "unexpected Str content"
96+
97+-- | Parse a sequence of inline elements between a string
98+-- @opener@ and a string @closer@, including inlines
99+-- between balanced pairs of @opener@ and a @closer@.
100+inlinesInBalanced :: String -> String -> GenParser Char ParserState [Inline]
101+inlinesInBalanced opener closer = try $ do
102+ string opener
103+ result <- manyTill ( (do lookAhead (inlineString opener)
104+ -- because it might be a link...
105+ bal <- inlinesInBalanced opener closer
106+ return $ [Str opener] ++ bal ++ [Str closer])
107+ <|> (count 1 inline))
108+ (try (string closer))
109+ return $ concat result
110+
111+--
112+-- document structure
113+--
114+
115+titleLine = try $ char '%' >> skipSpaces >> manyTill inline newline
116+
117+authorsLine = try $ do
118+ char '%'
119+ skipSpaces
120+ authors <- sepEndBy (many1 (noneOf ",;\n")) (oneOf ",;")
121+ newline
122+ return $ map (decodeCharacterReferences . removeLeadingTrailingSpace) authors
123+
124+dateLine = try $ do
125+ char '%'
126+ skipSpaces
127+ date <- many (noneOf "\n")
128+ newline
129+ return $ decodeCharacterReferences $ removeTrailingSpace date
130+
131+titleBlock = try $ do
132+ failIfStrict
133+ title <- option [] titleLine
134+ author <- option [] authorsLine
135+ date <- option "" dateLine
136+ optional blanklines
137+ return (title, author, date)
138+
139+parseMarkdown = do
140+ -- markdown allows raw HTML
141+ updateState (\state -> state { stateParseRaw = True })
142+ startPos <- getPosition
143+ -- go through once just to get list of reference keys
144+ -- docMinusKeys is the raw document with blanks where the keys were...
145+ docMinusKeys <- manyTill (referenceKey <|> lineClump) eof >>=
146+ return . concat
147+ setInput docMinusKeys
148+ setPosition startPos
149+ st <- getState
150+ -- go through again for notes unless strict...
151+ if stateStrict st
152+ then return ()
153+ else do docMinusNotes <- manyTill (noteBlock <|> lineClump) eof >>=
154+ return . concat
155+ st <- getState
156+ let reversedNotes = stateNotes st
157+ updateState $ \st -> st { stateNotes = reverse reversedNotes }
158+ setInput docMinusNotes
159+ setPosition startPos
160+ -- now parse it for real...
161+ (title, author, date) <- option ([],[],"") titleBlock
162+ blocks <- parseBlocks
163+ return $ Pandoc (Meta title author date) $ filter (/= Null) blocks
164+
165+--
166+-- initial pass for references and notes
167+--
168+
169+referenceKey = try $ do
170+ startPos <- getPosition
171+ nonindentSpaces
172+ label <- reference
173+ char ':'
174+ skipSpaces
175+ optional (char '<')
176+ src <- many (noneOf "> \n\t")
177+ optional (char '>')
178+ tit <- option "" referenceTitle
179+ blanklines
180+ endPos <- getPosition
181+ let newkey = (label, (removeTrailingSpace src, tit))
182+ st <- getState
183+ let oldkeys = stateKeys st
184+ updateState $ \st -> st { stateKeys = newkey : oldkeys }
185+ -- return blanks so line count isn't affected
186+ return $ replicate (sourceLine endPos - sourceLine startPos) '\n'
187+
188+referenceTitle = try $ do
189+ (many1 spaceChar >> option '\n' newline) <|> newline
190+ skipSpaces
191+ tit <- (charsInBalanced '(' ')' >>= return . unwords . words)
192+ <|> do delim <- char '\'' <|> char '"'
193+ manyTill anyChar (try (char delim >> skipSpaces >>
194+ notFollowedBy (noneOf ")\n")))
195+ return $ decodeCharacterReferences tit
196+
197+noteMarker = string "[^" >> manyTill (noneOf " \t\n") (char ']')
198+
199+rawLine = do
200+ notFollowedBy blankline
201+ notFollowedBy' noteMarker
202+ contents <- many1 nonEndline
203+ end <- option "" (newline >> optional indentSpaces >> return "\n")
204+ return $ contents ++ end
205+
206+rawLines = many1 rawLine >>= return . concat
207+
208+noteBlock = try $ do
209+ startPos <- getPosition
210+ ref <- noteMarker
211+ char ':'
212+ optional blankline
213+ optional indentSpaces
214+ raw <- sepBy rawLines (try (blankline >> indentSpaces))
215+ optional blanklines
216+ endPos <- getPosition
217+ -- parse the extracted text, which may contain various block elements:
218+ contents <- parseFromString parseBlocks $ (joinWithSep "\n" raw) ++ "\n\n"
219+ let newnote = (ref, contents)
220+ st <- getState
221+ let oldnotes = stateNotes st
222+ updateState $ \st -> st { stateNotes = newnote : oldnotes }
223+ -- return blanks so line count isn't affected
224+ return $ replicate (sourceLine endPos - sourceLine startPos) '\n'
225+
226+--
227+-- parsing blocks
228+--
229+
230+parseBlocks = manyTill block eof
231+
232+block = choice [ header
233+ , table
234+ , codeBlock
235+ , hrule
236+ , list
237+ , blockQuote
238+ , htmlBlock
239+ , rawLaTeXEnvironment'
240+ , para
241+ , plain
242+ , nullBlock ] <?> "block"
243+
244+--
245+-- header blocks
246+--
247+
248+header = atxHeader <|> setextHeader <?> "header"
249+
250+atxHeader = try $ do
251+ level <- many1 (char '#') >>= return . length
252+ notFollowedBy (char '.' <|> char ')') -- this would be a list
253+ skipSpaces
254+ text <- manyTill inline atxClosing >>= return . normalizeSpaces
255+ return $ Header level text
256+
257+atxClosing = try $ skipMany (char '#') >> blanklines
258+
259+setextHeader = try $ do
260+ -- first, see if this block has any chance of being a setextHeader:
261+ lookAhead (anyLine >> oneOf setextHChars)
262+ text <- many1Till inline newline >>= return . normalizeSpaces
263+ level <- choice $ zipWith
264+ (\ch lev -> try (many1 $ char ch) >> blanklines >> return lev)
265+ setextHChars [1..(length setextHChars)]
266+ return $ Header level text
267+
268+--
269+-- hrule block
270+--
271+
272+hrule = try $ do
273+ skipSpaces
274+ start <- oneOf hruleChars
275+ count 2 (skipSpaces >> char start)
276+ skipMany (skipSpaces >> char start)
277+ newline
278+ optional blanklines
279+ return HorizontalRule
280+
281+--
282+-- code blocks
283+--
284+
285+indentedLine = indentSpaces >> manyTill anyChar newline >>= return . (++ "\n")
286+
287+codeBlock = do
288+ contents <- many1 (indentedLine <|>
289+ try (do b <- blanklines
290+ l <- indentedLine
291+ return $ b ++ l))
292+ optional blanklines
293+ return $ CodeBlock $ stripTrailingNewlines $ concat contents
294+
295+--
296+-- block quotes
297+--
298+
299+emacsBoxQuote = try $ do
300+ failIfStrict
301+ string ",----"
302+ manyTill anyChar newline
303+ raw <- manyTill
304+ (try (char '|' >> optional (char ' ') >> manyTill anyChar newline))
305+ (try (string "`----"))
306+ blanklines
307+ return raw
308+
309+emailBlockQuoteStart = try $ nonindentSpaces >> char '>' >>~ optional (char ' ')
310+
311+emailBlockQuote = try $ do
312+ emailBlockQuoteStart
313+ raw <- sepBy (many (nonEndline <|>
314+ (try (endline >> notFollowedBy emailBlockQuoteStart >>
315+ return '\n'))))
316+ (try (newline >> emailBlockQuoteStart))
317+ newline <|> (eof >> return '\n')
318+ optional blanklines
319+ return raw
320+
321+blockQuote = do
322+ raw <- emailBlockQuote <|> emacsBoxQuote
323+ -- parse the extracted block, which may contain various block elements:
324+ contents <- parseFromString parseBlocks $ (joinWithSep "\n" raw) ++ "\n\n"
325+ return $ BlockQuote contents
326+
327+--
328+-- list blocks
329+--
330+
331+list = choice [ bulletList, orderedList, definitionList ] <?> "list"
332+
333+bulletListStart = try $ do
334+ optional newline -- if preceded by a Plain block in a list context
335+ nonindentSpaces
336+ notFollowedBy' hrule -- because hrules start out just like lists
337+ oneOf bulletListMarkers
338+ spaceChar
339+ skipSpaces
340+
341+anyOrderedListStart = try $ do
342+ optional newline -- if preceded by a Plain block in a list context
343+ nonindentSpaces
344+ notFollowedBy $ string "p." >> spaceChar >> digit -- page number
345+ state <- getState
346+ if stateStrict state
347+ then do many1 digit
348+ char '.'
349+ spaceChar
350+ return (1, DefaultStyle, DefaultDelim)
351+ else anyOrderedListMarker >>~ spaceChar
352+
353+orderedListStart style delim = try $ do
354+ optional newline -- if preceded by a Plain block in a list context
355+ nonindentSpaces
356+ state <- getState
357+ num <- if stateStrict state
358+ then do many1 digit
359+ char '.'
360+ return 1
361+ else orderedListMarker style delim
362+ if delim == Period && (style == UpperAlpha || (style == UpperRoman &&
363+ num `elem` [1, 5, 10, 50, 100, 500, 1000]))
364+ then char '\t' <|> (spaceChar >> spaceChar)
365+ else spaceChar
366+ skipSpaces
367+
368+-- parse a line of a list item (start = parser for beginning of list item)
369+listLine start = try $ do
370+ notFollowedBy' start
371+ notFollowedBy blankline
372+ notFollowedBy' (do indentSpaces
373+ many (spaceChar)
374+ bulletListStart <|> (anyOrderedListStart >> return ()))
375+ line <- manyTill anyChar newline
376+ return $ line ++ "\n"
377+
378+-- parse raw text for one list item, excluding start marker and continuations
379+rawListItem start = try $ do
380+ start
381+ result <- many1 (listLine start)
382+ blanks <- many blankline
383+ return $ concat result ++ blanks
384+
385+-- continuation of a list item - indented and separated by blankline
386+-- or (in compact lists) endline.
387+-- note: nested lists are parsed as continuations
388+listContinuation start = try $ do
389+ lookAhead indentSpaces
390+ result <- many1 (listContinuationLine start)
391+ blanks <- many blankline
392+ return $ concat result ++ blanks
393+
394+listContinuationLine start = try $ do
395+ notFollowedBy blankline
396+ notFollowedBy' start
397+ optional indentSpaces
398+ result <- manyTill anyChar newline
399+ return $ result ++ "\n"
400+
401+listItem start = try $ do
402+ first <- rawListItem start
403+ continuations <- many (listContinuation start)
404+ -- parsing with ListItemState forces markers at beginning of lines to
405+ -- count as list item markers, even if not separated by blank space.
406+ -- see definition of "endline"
407+ state <- getState
408+ let oldContext = stateParserContext state
409+ setState $ state {stateParserContext = ListItemState}
410+ -- parse the extracted block, which may contain various block elements:
411+ let raw = concat (first:continuations)
412+ contents <- parseFromString parseBlocks raw
413+ updateState (\st -> st {stateParserContext = oldContext})
414+ return contents
415+
416+orderedList = try $ do
417+ (start, style, delim) <- lookAhead anyOrderedListStart
418+ items <- many1 (listItem (orderedListStart style delim))
419+ return $ OrderedList (start, style, delim) $ compactify items
420+
421+bulletList = many1 (listItem bulletListStart) >>=
422+ return . BulletList . compactify
423+
424+-- definition lists
425+
426+definitionListItem = try $ do
427+ notFollowedBy blankline
428+ notFollowedBy' indentSpaces
429+ -- first, see if this has any chance of being a definition list:
430+ lookAhead (anyLine >> char ':')
431+ term <- manyTill inline newline
432+ raw <- many1 defRawBlock
433+ state <- getState
434+ let oldContext = stateParserContext state
435+ -- parse the extracted block, which may contain various block elements:
436+ contents <- parseFromString parseBlocks $ concat raw
437+ updateState (\st -> st {stateParserContext = oldContext})
438+ return ((normalizeSpaces term), contents)
439+
440+defRawBlock = try $ do
441+ char ':'
442+ state <- getState
443+ let tabStop = stateTabStop state
444+ try (count (tabStop - 1) (char ' ')) <|> (many (char ' ') >> string "\t")
445+ firstline <- anyLine
446+ rawlines <- many (notFollowedBy blankline >> indentSpaces >> anyLine)
447+ trailing <- option "" blanklines
448+ return $ firstline ++ "\n" ++ unlines rawlines ++ trailing
449+
450+definitionList = do
451+ failIfStrict
452+ items <- many1 definitionListItem
453+ let (terms, defs) = unzip items
454+ let defs' = compactify defs
455+ let items' = zip terms defs'
456+ return $ DefinitionList items'
457+
458+--
459+-- paragraph block
460+--
461+
462+para = try $ do
463+ result <- many1 inline
464+ newline
465+ blanklines <|> do st <- getState
466+ if stateStrict st
467+ then lookAhead (blockQuote <|> header) >> return ""
468+ else lookAhead emacsBoxQuote >> return ""
469+ return $ Para $ normalizeSpaces result
470+
471+plain = many1 inline >>= return . Plain . normalizeSpaces
472+
473+--
474+-- raw html
475+--
476+
477+htmlElement = strictHtmlBlock <|> htmlBlockElement <?> "html element"
478+
479+htmlBlock = do
480+ st <- getState
481+ if stateStrict st
482+ then try $ do failUnlessBeginningOfLine
483+ first <- htmlElement
484+ finalSpace <- many (oneOf spaceChars)
485+ finalNewlines <- many newline
486+ return $ RawHtml $ first ++ finalSpace ++ finalNewlines
487+ else rawHtmlBlocks
488+
489+-- True if tag is self-closing
490+isSelfClosing tag =
491+ isSuffixOf "/>" $ filter (not . (`elem` " \n\t")) tag
492+
493+strictHtmlBlock = try $ do
494+ tag <- anyHtmlBlockTag
495+ let tag' = extractTagType tag
496+ if isSelfClosing tag || tag' == "hr"
497+ then return tag
498+ else do contents <- many (notFollowedBy' (htmlEndTag tag') >>
499+ (htmlElement <|> (count 1 anyChar)))
500+ end <- htmlEndTag tag'
501+ return $ tag ++ concat contents ++ end
502+
503+rawHtmlBlocks = do
504+ htmlBlocks <- many1 rawHtmlBlock
505+ let combined = concatMap (\(RawHtml str) -> str) htmlBlocks
506+ let combined' = if not (null combined) && last combined == '\n'
507+ then init combined -- strip extra newline
508+ else combined
509+ return $ RawHtml combined'
510+
511+--
512+-- LaTeX
513+--
514+
515+rawLaTeXEnvironment' = failIfStrict >> rawLaTeXEnvironment
516+
517+--
518+-- Tables
519+--
520+
521+-- Parse a dashed line with optional trailing spaces; return its length
522+-- and the length including trailing space.
523+dashedLine ch = do
524+ dashes <- many1 (char ch)
525+ sp <- many spaceChar
526+ return $ (length dashes, length $ dashes ++ sp)
527+
528+-- Parse a table header with dashed lines of '-' preceded by
529+-- one line of text.
530+simpleTableHeader = try $ do
531+ rawContent <- anyLine
532+ initSp <- nonindentSpaces
533+ dashes <- many1 (dashedLine '-')
534+ newline
535+ let (lengths, lines) = unzip dashes
536+ let indices = scanl (+) (length initSp) lines
537+ let rawHeads = tail $ splitByIndices (init indices) rawContent
538+ let aligns = zipWith alignType (map (\a -> [a]) rawHeads) lengths
539+ return (rawHeads, aligns, indices)
540+
541+-- Parse a table footer - dashed lines followed by blank line.
542+tableFooter = try $ nonindentSpaces >> many1 (dashedLine '-') >> blanklines
543+
544+-- Parse a table separator - dashed line.
545+tableSep = try $ nonindentSpaces >> many1 (dashedLine '-') >> string "\n"
546+
547+-- Parse a raw line and split it into chunks by indices.
548+rawTableLine indices = do
549+ notFollowedBy' (blanklines <|> tableFooter)
550+ line <- many1Till anyChar newline
551+ return $ map removeLeadingTrailingSpace $ tail $
552+ splitByIndices (init indices) line
553+
554+-- Parse a table line and return a list of lists of blocks (columns).
555+tableLine indices = rawTableLine indices >>= mapM (parseFromString (many plain))
556+
557+-- Parse a multiline table row and return a list of blocks (columns).
558+multilineRow indices = do
559+ colLines <- many1 (rawTableLine indices)
560+ optional blanklines
561+ let cols = map unlines $ transpose colLines
562+ mapM (parseFromString (many plain)) cols
563+
564+-- Calculate relative widths of table columns, based on indices
565+widthsFromIndices :: Int -- Number of columns on terminal
566+ -> [Int] -- Indices
567+ -> [Float] -- Fractional relative sizes of columns
568+widthsFromIndices _ [] = []
569+widthsFromIndices numColumns indices =
570+ let lengths = zipWith (-) indices (0:indices)
571+ totLength = sum lengths
572+ quotient = if totLength > numColumns
573+ then fromIntegral totLength
574+ else fromIntegral numColumns
575+ fracs = map (\l -> (fromIntegral l) / quotient) lengths in
576+ tail fracs
577+
578+-- Parses a table caption: inlines beginning with 'Table:'
579+-- and followed by blank lines.
580+tableCaption = try $ do
581+ nonindentSpaces
582+ string "Table:"
583+ result <- many1 inline
584+ blanklines
585+ return $ normalizeSpaces result
586+
587+-- Parse a table using 'headerParser', 'lineParser', and 'footerParser'.
588+tableWith headerParser lineParser footerParser = try $ do
589+ (rawHeads, aligns, indices) <- headerParser
590+ lines <- many1Till (lineParser indices) footerParser
591+ caption <- option [] tableCaption
592+ heads <- mapM (parseFromString (many plain)) rawHeads
593+ state <- getState
594+ let numColumns = stateColumns state
595+ let widths = widthsFromIndices numColumns indices
596+ return $ Table caption aligns widths heads lines
597+
598+-- Parse a simple table with '---' header and one line per row.
599+simpleTable = tableWith simpleTableHeader tableLine blanklines
600+
601+-- Parse a multiline table: starts with row of '-' on top, then header
602+-- (which may be multiline), then the rows,
603+-- which may be multiline, separated by blank lines, and
604+-- ending with a footer (dashed line followed by blank line).
605+multilineTable = tableWith multilineTableHeader multilineRow tableFooter
606+
607+multilineTableHeader = try $ do
608+ tableSep
609+ rawContent <- many1 (notFollowedBy' tableSep >> many1Till anyChar newline)
610+ initSp <- nonindentSpaces
611+ dashes <- many1 (dashedLine '-')
612+ newline
613+ let (lengths, lines) = unzip dashes
614+ let indices = scanl (+) (length initSp) lines
615+ let rawHeadsList = transpose $ map
616+ (\ln -> tail $ splitByIndices (init indices) ln)
617+ rawContent
618+ let rawHeads = map (joinWithSep " ") rawHeadsList
619+ let aligns = zipWith alignType rawHeadsList lengths
620+ return ((map removeLeadingTrailingSpace rawHeads), aligns, indices)
621+
622+-- Returns an alignment type for a table, based on a list of strings
623+-- (the rows of the column header) and a number (the length of the
624+-- dashed line under the rows.
625+alignType :: [String] -> Int -> Alignment
626+alignType [] len = AlignDefault
627+alignType strLst len =
628+ let str = head $ sortBy (comparing length) $
629+ map removeTrailingSpace strLst
630+ leftSpace = if null str then False else (str !! 0) `elem` " \t"
631+ rightSpace = length str < len || (str !! (len - 1)) `elem` " \t"
632+ in case (leftSpace, rightSpace) of
633+ (True, False) -> AlignRight
634+ (False, True) -> AlignLeft
635+ (True, True) -> AlignCenter
636+ (False, False) -> AlignDefault
637+
638+table = failIfStrict >> (simpleTable <|> multilineTable) <?> "table"
639+
640+--
641+-- inline
642+--
643+
644+inline = choice [ str
645+ , smartPunctuation
646+ , whitespace
647+ , endline
648+ , code
649+ , charRef
650+ , strong
651+ , emph
652+ , note
653+ , inlineNote
654+ , link
655+ , image
656+ , math
657+ , strikeout
658+ , superscript
659+ , subscript
660+ , autoLink
661+ , rawHtmlInline'
662+ , rawLaTeXInline'
663+ , escapedChar
664+ , symbol
665+ , ltSign ] <?> "inline"
666+
667+escapedChar = do
668+ char '\\'
669+ state <- getState
670+ result <- option '\\' $ if stateStrict state
671+ then oneOf "\\`*_{}[]()>#+-.!~"
672+ else satisfy (not . isAlphaNum)
673+ return $ Str [result]
674+
675+ltSign = do
676+ st <- getState
677+ if stateStrict st
678+ then char '<'
679+ else notFollowedBy' rawHtmlBlocks >> char '<' -- unless it starts html
680+ return $ Str ['<']
681+
682+specialCharsMinusLt = filter (/= '<') specialChars
683+
684+symbol = do
685+ result <- oneOf specialCharsMinusLt
686+ return $ Str [result]
687+
688+-- parses inline code, between n `s and n `s
689+code = try $ do
690+ starts <- many1 (char '`')
691+ skipSpaces
692+ result <- many1Till (many1 (noneOf "`\n") <|> many1 (char '`') <|>
693+ (char '\n' >> return " "))
694+ (try (skipSpaces >> count (length starts) (char '`') >>
695+ notFollowedBy (char '`')))
696+ return $ Code $ removeLeadingTrailingSpace $ concat result
697+
698+mathWord = many1 ((noneOf " \t\n\\$") <|>
699+ (try (char '\\') >>~ notFollowedBy (char '$')))
700+
701+math = try $ do
702+ failIfStrict
703+ char '$'
704+ notFollowedBy space
705+ words <- sepBy1 mathWord (many1 space)
706+ char '$'
707+ return $ TeX ("$" ++ (joinWithSep " " words) ++ "$")
708+
709+emph = ((enclosed (char '*') (char '*') inline) <|>
710+ (enclosed (char '_') (char '_' >> notFollowedBy alphaNum) inline)) >>=
711+ return . Emph . normalizeSpaces
712+
713+strong = ((enclosed (string "**") (try $ string "**") inline) <|>
714+ (enclosed (string "__") (try $ string "__") inline)) >>=
715+ return . Strong . normalizeSpaces
716+
717+strikeout = failIfStrict >> enclosed (string "~~") (try $ string "~~") inline >>=
718+ return . Strikeout . normalizeSpaces
719+
720+superscript = failIfStrict >> enclosed (char '^') (char '^')
721+ (notFollowedBy' whitespace >> inline) >>= -- may not contain Space
722+ return . Superscript
723+
724+subscript = failIfStrict >> enclosed (char '~') (char '~')
725+ (notFollowedBy' whitespace >> inline) >>= -- may not contain Space
726+ return . Subscript
727+
728+smartPunctuation = failUnlessSmart >>
729+ choice [ quoted, apostrophe, dash, ellipses ]
730+
731+apostrophe = (char '\'' <|> char '\8217') >> return Apostrophe
732+
733+quoted = doubleQuoted <|> singleQuoted
734+
735+withQuoteContext context parser = do
736+ oldState <- getState
737+ let oldQuoteContext = stateQuoteContext oldState
738+ setState oldState { stateQuoteContext = context }
739+ result <- parser
740+ newState <- getState
741+ setState newState { stateQuoteContext = oldQuoteContext }
742+ return result
743+
744+singleQuoted = try $ do
745+ singleQuoteStart
746+ withQuoteContext InSingleQuote $ many1Till inline singleQuoteEnd >>=
747+ return . Quoted SingleQuote . normalizeSpaces
748+
749+doubleQuoted = try $ do
750+ doubleQuoteStart
751+ withQuoteContext InDoubleQuote $ many1Till inline doubleQuoteEnd >>=
752+ return . Quoted DoubleQuote . normalizeSpaces
753+
754+failIfInQuoteContext context = do
755+ st <- getState
756+ if stateQuoteContext st == context
757+ then fail "already inside quotes"
758+ else return ()
759+
760+singleQuoteStart = do
761+ failIfInQuoteContext InSingleQuote
762+ char '\8216' <|>
763+ do char '\''
764+ notFollowedBy (oneOf ")!],.;:-? \t\n")
765+ notFollowedBy (try (oneOfStrings ["s","t","m","ve","ll","re"] >>
766+ satisfy (not . isAlphaNum))) -- possess/contraction
767+ return '\''
768+
769+singleQuoteEnd = (char '\'' <|> char '\8217') >> notFollowedBy alphaNum
770+
771+doubleQuoteStart = failIfInQuoteContext InDoubleQuote >>
772+ (char '"' <|> char '\8220') >>
773+ notFollowedBy (oneOf " \t\n")
774+
775+doubleQuoteEnd = char '"' <|> char '\8221'
776+
777+ellipses = oneOfStrings ["...", " . . . ", ". . .", " . . ."] >> return Ellipses
778+
779+dash = enDash <|> emDash
780+
781+enDash = try $ char '-' >> notFollowedBy (noneOf "0123456789") >> return EnDash
782+
783+emDash = try $ skipSpaces >> oneOfStrings ["---", "--"] >>
784+ skipSpaces >> return EmDash
785+
786+whitespace = do
787+ sps <- many1 (oneOf spaceChars)
788+ if length sps >= 2
789+ then option Space (endline >> return LineBreak)
790+ else return Space <?> "whitespace"
791+
792+nonEndline = satisfy (/='\n')
793+
794+strChar = noneOf (specialChars ++ spaceChars ++ "\n")
795+
796+str = many1 strChar >>= return . Str
797+
798+-- an endline character that can be treated as a space, not a structural break
799+endline = try $ do
800+ newline
801+ notFollowedBy blankline
802+ st <- getState
803+ if stateStrict st
804+ then do notFollowedBy emailBlockQuoteStart
805+ notFollowedBy (char '#') -- atx header
806+ else return ()
807+ -- parse potential list-starts differently if in a list:
808+ if stateParserContext st == ListItemState
809+ then notFollowedBy' (bulletListStart <|>
810+ (anyOrderedListStart >> return ()))
811+ else return ()
812+ return Space
813+
814+--
815+-- links
816+--
817+
818+-- a reference label for a link
819+reference = notFollowedBy' (string "[^") >> -- footnote reference
820+ inlinesInBalanced "[" "]" >>= (return . normalizeSpaces)
821+
822+-- source for a link, with optional title
823+source = try $ do
824+ char '('
825+ optional (char '<')
826+ src <- many (noneOf ")> \t\n")
827+ optional (char '>')
828+ tit <- option "" linkTitle
829+ skipSpaces
830+ char ')'
831+ return (removeTrailingSpace src, tit)
832+
833+linkTitle = try $ do
834+ (many1 spaceChar >> option '\n' newline) <|> newline
835+ skipSpaces
836+ delim <- char '\'' <|> char '"'
837+ tit <- manyTill anyChar (try (char delim >> skipSpaces >>
838+ notFollowedBy (noneOf ")\n")))
839+ return $ decodeCharacterReferences tit
840+
841+link = try $ do
842+ label <- reference
843+ src <- source <|> referenceLink label
844+ return $ Link label src
845+
846+-- a link like [this][ref] or [this][] or [this]
847+referenceLink label = do
848+ ref <- option [] (try (optional (char ' ') >>
849+ optional (newline >> skipSpaces) >> reference))
850+ let ref' = if null ref then label else ref
851+ state <- getState
852+ case lookupKeySrc (stateKeys state) ref' of
853+ Nothing -> fail "no corresponding key"
854+ Just target -> return target
855+
856+emailAddress = try $ do
857+ name <- many1 (alphaNum <|> char '+')
858+ char '@'
859+ first <- many1 alphaNum
860+ rest <- many1 (char '.' >> many1 alphaNum)
861+ return $ "mailto:" ++ name ++ "@" ++ joinWithSep "." (first:rest)
862+
863+uri = try $ do
864+ str <- many1 (noneOf "\n\t >")
865+ if isURI str
866+ then return str
867+ else fail "not a URI"
868+
869+autoLink = try $ do
870+ char '<'
871+ src <- uri <|> emailAddress
872+ char '>'
873+ let src' = if "mailto:" `isPrefixOf` src
874+ then drop 7 src
875+ else src
876+ st <- getState
877+ return $ if stateStrict st
878+ then Link [Str src'] (src, "")
879+ else Link [Code src'] (src, "")
880+
881+image = try $ do
882+ char '!'
883+ (Link label src) <- link
884+ return $ Image label src
885+
886+note = try $ do
887+ failIfStrict
888+ ref <- noteMarker
889+ state <- getState
890+ let notes = stateNotes state
891+ case lookup ref notes of
892+ Nothing -> fail "note not found"
893+ Just contents -> return $ Note contents
894+
895+inlineNote = try $ do
896+ failIfStrict
897+ char '^'
898+ contents <- inlinesInBalanced "[" "]"
899+ return $ Note [Para contents]
900+
901+rawLaTeXInline' = failIfStrict >> rawLaTeXInline
902+
903+rawHtmlInline' = do
904+ st <- getState
905+ result <- choice $ if stateStrict st
906+ then [htmlBlockElement, anyHtmlTag, anyHtmlEndTag]
907+ else [htmlBlockElement, anyHtmlInlineTag]
908+ return $ HtmlInline result
909+
src/Text/Pandoc/Readers/RST.hsnew file+640
@@ -0,0 +1,640 @@
1+{-
2+Copyright (C) 2006-7 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.Readers.RST
21+ Copyright : Copyright (C) 2006-7 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 from reStructuredText to 'Pandoc' document.
29+-}
30+module Text.Pandoc.Readers.RST (
31+ readRST
32+ ) where
33+import Text.Pandoc.Definition
34+import Text.Pandoc.Shared
35+import Text.ParserCombinators.Parsec
36+import Data.List ( findIndex, delete )
37+
38+-- | Parse reStructuredText string and return Pandoc document.
39+readRST :: ParserState -> String -> Pandoc
40+readRST state str = (readWith parseRST) state (str ++ "\n\n")
41+
42+--
43+-- Constants and data structure definitions
44+---
45+
46+bulletListMarkers = "*+-"
47+underlineChars = "!\"#$&'()*+,-./:;<=>?@[\\]^_`{|}~"
48+
49+-- treat these as potentially non-text when parsing inline:
50+specialChars = "\\`|*_<>$:[-"
51+
52+--
53+-- parsing documents
54+--
55+
56+isAnonKey (ref, src) = ref == [Str "_"]
57+
58+isHeader :: Int -> Block -> Bool
59+isHeader n (Header x _) = x == n
60+isHeader _ _ = False
61+
62+-- | Promote all headers in a list of blocks. (Part of
63+-- title transformation for RST.)
64+promoteHeaders :: Int -> [Block] -> [Block]
65+promoteHeaders num ((Header level text):rest) =
66+ (Header (level - num) text):(promoteHeaders num rest)
67+promoteHeaders num (other:rest) = other:(promoteHeaders num rest)
68+promoteHeaders num [] = []
69+
70+-- | If list of blocks starts with a header (or a header and subheader)
71+-- of level that are not found elsewhere, return it as a title and
72+-- promote all the other headers.
73+titleTransform :: [Block] -- ^ list of blocks
74+ -> ([Block], [Inline]) -- ^ modified list of blocks, title
75+titleTransform ((Header 1 head1):(Header 2 head2):rest) = -- title subtitle
76+ if (any (isHeader 1) rest) || (any (isHeader 2) rest)
77+ then ((Header 1 head1):(Header 2 head2):rest, [])
78+ else ((promoteHeaders 2 rest), head1 ++ [Str ":", Space] ++ head2)
79+titleTransform ((Header 1 head1):rest) = -- title, no subtitle
80+ if (any (isHeader 1) rest)
81+ then ((Header 1 head1):rest, [])
82+ else ((promoteHeaders 1 rest), head1)
83+titleTransform blocks = (blocks, [])
84+
85+parseRST = do
86+ startPos <- getPosition
87+ -- go through once just to get list of reference keys
88+ -- docMinusKeys is the raw document with blanks where the keys were...
89+ docMinusKeys <- manyTill (referenceKey <|> lineClump) eof >>= return . concat
90+ setInput docMinusKeys
91+ setPosition startPos
92+ st <- getState
93+ let reversedKeys = stateKeys st
94+ updateState $ \st -> st { stateKeys = reverse reversedKeys }
95+ -- now parse it for real...
96+ blocks <- parseBlocks
97+ let blocks' = filter (/= Null) blocks
98+ state <- getState
99+ let (blocks'', title) = if stateStandalone state
100+ then titleTransform blocks'
101+ else (blocks', [])
102+ let authors = stateAuthors state
103+ let date = stateDate state
104+ let title' = if (null title) then (stateTitle state) else title
105+ return $ Pandoc (Meta title' authors date) blocks''
106+
107+--
108+-- parsing blocks
109+--
110+
111+parseBlocks = manyTill block eof
112+
113+block = choice [ codeBlock
114+ , rawHtmlBlock
115+ , rawLaTeXBlock
116+ , fieldList
117+ , blockQuote
118+ , imageBlock
119+ , unknownDirective
120+ , header
121+ , hrule
122+ , list
123+ , lineBlock
124+ , para
125+ , plain
126+ , nullBlock ] <?> "block"
127+
128+--
129+-- field list
130+--
131+
132+fieldListItem indent = try $ do
133+ string indent
134+ char ':'
135+ name <- many1 alphaNum
136+ string ": "
137+ skipSpaces
138+ first <- manyTill anyChar newline
139+ rest <- option "" $ try $ lookAhead (string indent >> oneOf " \t") >>
140+ indentedBlock
141+ return (name, joinWithSep " " (first:(lines rest)))
142+
143+fieldList = try $ do
144+ indent <- lookAhead $ many (oneOf " \t")
145+ items <- many1 $ fieldListItem indent
146+ blanklines
147+ let authors = case lookup "Authors" items of
148+ Just auth -> [auth]
149+ Nothing -> map snd (filter (\(x,y) -> x == "Author") items)
150+ if null authors
151+ then return ()
152+ else updateState $ \st -> st {stateAuthors = authors}
153+ case (lookup "Date" items) of
154+ Just dat -> updateState $ \st -> st {stateDate = dat}
155+ Nothing -> return ()
156+ case (lookup "Title" items) of
157+ Just tit -> parseFromString (many inline) tit >>=
158+ \t -> updateState $ \st -> st {stateTitle = t}
159+ Nothing -> return ()
160+ let remaining = filter (\(x,y) -> (x /= "Authors") && (x /= "Author") &&
161+ (x /= "Date") && (x /= "Title")) items
162+ if null remaining
163+ then return Null
164+ else do terms <- mapM (return . (:[]) . Str . fst) remaining
165+ defs <- mapM (parseFromString (many block) . snd)
166+ remaining
167+ return $ DefinitionList $ zip terms defs
168+
169+--
170+-- line block
171+--
172+
173+lineBlockLine = try $ do
174+ string "| "
175+ white <- many (oneOf " \t")
176+ line <- manyTill inline newline
177+ return $ (if null white then [] else [Str white]) ++ line ++ [LineBreak]
178+
179+lineBlock = try $ do
180+ lines <- many1 lineBlockLine
181+ blanklines
182+ return $ Para (concat lines)
183+
184+--
185+-- paragraph block
186+--
187+
188+para = paraBeforeCodeBlock <|> paraNormal <?> "paragraph"
189+
190+codeBlockStart = string "::" >> blankline >> blankline
191+
192+-- paragraph that ends in a :: starting a code block
193+paraBeforeCodeBlock = try $ do
194+ result <- many1 (notFollowedBy' codeBlockStart >> inline)
195+ lookAhead (string "::")
196+ return $ Para $ if last result == Space
197+ then normalizeSpaces result
198+ else (normalizeSpaces result) ++ [Str ":"]
199+
200+-- regular paragraph
201+paraNormal = try $ do
202+ result <- many1 inline
203+ newline
204+ blanklines
205+ return $ Para $ normalizeSpaces result
206+
207+plain = many1 inline >>= return . Plain . normalizeSpaces
208+
209+--
210+-- image block
211+--
212+
213+imageBlock = try $ do
214+ string ".. image:: "
215+ src <- manyTill anyChar newline
216+ fields <- option [] $ do indent <- lookAhead $ many (oneOf " /t")
217+ many1 $ fieldListItem indent
218+ optional blanklines
219+ case lookup "alt" fields of
220+ Just alt -> return $ Plain [Image [Str alt] (src, alt)]
221+ Nothing -> return $ Plain [Image [Str "image"] (src, "")]
222+--
223+-- header blocks
224+--
225+
226+header = doubleHeader <|> singleHeader <?> "header"
227+
228+-- a header with lines on top and bottom
229+doubleHeader = try $ do
230+ c <- oneOf underlineChars
231+ rest <- many (char c) -- the top line
232+ let lenTop = length (c:rest)
233+ skipSpaces
234+ newline
235+ txt <- many1 (notFollowedBy blankline >> inline)
236+ pos <- getPosition
237+ let len = (sourceColumn pos) - 1
238+ if (len > lenTop) then fail "title longer than border" else return ()
239+ blankline -- spaces and newline
240+ count lenTop (char c) -- the bottom line
241+ blanklines
242+ -- check to see if we've had this kind of header before.
243+ -- if so, get appropriate level. if not, add to list.
244+ state <- getState
245+ let headerTable = stateHeaderTable state
246+ let (headerTable',level) = case findIndex (== DoubleHeader c) headerTable of
247+ Just ind -> (headerTable, ind + 1)
248+ Nothing -> (headerTable ++ [DoubleHeader c], (length headerTable) + 1)
249+ setState (state { stateHeaderTable = headerTable' })
250+ return $ Header level (normalizeSpaces txt)
251+
252+-- a header with line on the bottom only
253+singleHeader = try $ do
254+ notFollowedBy' whitespace
255+ txt <- many1 (do {notFollowedBy blankline; inline})
256+ pos <- getPosition
257+ let len = (sourceColumn pos) - 1
258+ blankline
259+ c <- oneOf underlineChars
260+ rest <- count (len - 1) (char c)
261+ many (char c)
262+ blanklines
263+ state <- getState
264+ let headerTable = stateHeaderTable state
265+ let (headerTable',level) = case findIndex (== SingleHeader c) headerTable of
266+ Just ind -> (headerTable, ind + 1)
267+ Nothing -> (headerTable ++ [SingleHeader c], (length headerTable) + 1)
268+ setState (state { stateHeaderTable = headerTable' })
269+ return $ Header level (normalizeSpaces txt)
270+
271+--
272+-- hrule block
273+--
274+
275+hrule = try $ do
276+ chr <- oneOf underlineChars
277+ count 3 (char chr)
278+ skipMany (char chr)
279+ blankline
280+ blanklines
281+ return HorizontalRule
282+
283+--
284+-- code blocks
285+--
286+
287+-- read a line indented by a given string
288+indentedLine indents = try $ do
289+ string indents
290+ result <- manyTill anyChar newline
291+ return $ result ++ "\n"
292+
293+-- two or more indented lines, possibly separated by blank lines.
294+-- any amount of indentation will work.
295+indentedBlock = do
296+ indents <- lookAhead $ many1 (oneOf " \t")
297+ lns <- many $ choice $ [ indentedLine indents,
298+ try $ do b <- blanklines
299+ l <- indentedLine indents
300+ return (b ++ l) ]
301+ optional blanklines
302+ return $ concat lns
303+
304+codeBlock = try $ do
305+ codeBlockStart
306+ result <- indentedBlock
307+ return $ CodeBlock $ stripTrailingNewlines result
308+
309+--
310+-- raw html
311+--
312+
313+rawHtmlBlock = try $ string ".. raw:: html" >> blanklines >>
314+ indentedBlock >>= return . RawHtml
315+
316+--
317+-- raw latex
318+--
319+
320+rawLaTeXBlock = try $ do
321+ string ".. raw:: latex"
322+ blanklines
323+ result <- indentedBlock
324+ return $ Para [(TeX result)]
325+
326+--
327+-- block quotes
328+--
329+
330+blockQuote = do
331+ raw <- indentedBlock
332+ -- parse the extracted block, which may contain various block elements:
333+ contents <- parseFromString parseBlocks $ raw ++ "\n\n"
334+ return $ BlockQuote contents
335+
336+--
337+-- list blocks
338+--
339+
340+list = choice [ bulletList, orderedList, definitionList ] <?> "list"
341+
342+definitionListItem = try $ do
343+ term <- many1Till inline endline
344+ raw <- indentedBlock
345+ -- parse the extracted block, which may contain various block elements:
346+ contents <- parseFromString parseBlocks $ raw ++ "\n\n"
347+ return (normalizeSpaces term, contents)
348+
349+definitionList = many1 definitionListItem >>= return . DefinitionList
350+
351+-- parses bullet list start and returns its length (inc. following whitespace)
352+bulletListStart = try $ do
353+ notFollowedBy' hrule -- because hrules start out just like lists
354+ marker <- oneOf bulletListMarkers
355+ white <- many1 spaceChar
356+ return $ length (marker:white)
357+
358+-- parses ordered list start and returns its length (inc following whitespace)
359+orderedListStart style delim = try $ do
360+ (_, markerLen) <- withHorizDisplacement (orderedListMarker style delim)
361+ white <- many1 spaceChar
362+ return $ markerLen + length white
363+
364+-- parse a line of a list item
365+listLine markerLength = try $ do
366+ notFollowedBy blankline
367+ indentWith markerLength
368+ line <- manyTill anyChar newline
369+ return $ line ++ "\n"
370+
371+-- indent by specified number of spaces (or equiv. tabs)
372+indentWith num = do
373+ state <- getState
374+ let tabStop = stateTabStop state
375+ if (num < tabStop)
376+ then count num (char ' ')
377+ else choice [ try (count num (char ' ')),
378+ (try (char '\t' >> count (num - tabStop) (char ' '))) ]
379+
380+-- parse raw text for one list item, excluding start marker and continuations
381+rawListItem start = do
382+ markerLength <- start
383+ firstLine <- manyTill anyChar newline
384+ restLines <- many (listLine markerLength)
385+ return (markerLength, (firstLine ++ "\n" ++ (concat restLines)))
386+
387+-- continuation of a list item - indented and separated by blankline or
388+-- (in compact lists) endline.
389+-- Note: nested lists are parsed as continuations.
390+listContinuation markerLength = try $ do
391+ blanks <- many1 blankline
392+ result <- many1 (listLine markerLength)
393+ return $ blanks ++ concat result
394+
395+listItem start = try $ do
396+ (markerLength, first) <- rawListItem start
397+ rest <- many (listContinuation markerLength)
398+ blanks <- choice [ try (many blankline >>~ lookAhead start),
399+ many1 blankline ] -- whole list must end with blank.
400+ -- parsing with ListItemState forces markers at beginning of lines to
401+ -- count as list item markers, even if not separated by blank space.
402+ -- see definition of "endline"
403+ state <- getState
404+ let oldContext = stateParserContext state
405+ setState $ state {stateParserContext = ListItemState}
406+ -- parse the extracted block, which may itself contain block elements
407+ parsed <- parseFromString parseBlocks $ concat (first:rest) ++ blanks
408+ updateState (\st -> st {stateParserContext = oldContext})
409+ return parsed
410+
411+orderedList = do
412+ (start, style, delim) <- lookAhead (anyOrderedListMarker >>~ spaceChar)
413+ items <- many1 (listItem (orderedListStart style delim))
414+ let items' = compactify items
415+ return $ OrderedList (start, style, delim) items'
416+
417+bulletList = many1 (listItem bulletListStart) >>=
418+ return . BulletList . compactify
419+
420+--
421+-- unknown directive (e.g. comment)
422+--
423+
424+unknownDirective = try $ do
425+ string ".. "
426+ manyTill anyChar newline
427+ many (string " :" >> many1 (noneOf "\n:") >> char ':' >>
428+ many1 (noneOf "\n") >> newline)
429+ optional blanklines
430+ return Null
431+
432+--
433+-- reference key
434+--
435+
436+referenceKey = do
437+ startPos <- getPosition
438+ key <- choice [imageKey, anonymousKey, regularKeyQuoted, regularKey]
439+ st <- getState
440+ let oldkeys = stateKeys st
441+ updateState $ \st -> st { stateKeys = key : oldkeys }
442+ optional blanklines
443+ endPos <- getPosition
444+ -- return enough blanks to replace key
445+ return $ replicate (sourceLine endPos - sourceLine startPos) '\n'
446+
447+targetURI = do
448+ skipSpaces
449+ optional newline
450+ contents <- many1 (try (many spaceChar >> newline >>
451+ many1 spaceChar >> noneOf " \t\n") <|> noneOf "\n")
452+ blanklines
453+ return contents
454+
455+imageKey = try $ do
456+ string ".. |"
457+ ref <- manyTill inline (char '|')
458+ skipSpaces
459+ string "image::"
460+ src <- targetURI
461+ return (normalizeSpaces ref, (removeLeadingTrailingSpace src, ""))
462+
463+anonymousKey = try $ do
464+ oneOfStrings [".. __:", "__"]
465+ src <- targetURI
466+ state <- getState
467+ return ([Str "_"], (removeLeadingTrailingSpace src, ""))
468+
469+regularKeyQuoted = try $ do
470+ string ".. _`"
471+ ref <- manyTill inline (char '`')
472+ char ':'
473+ src <- targetURI
474+ return (normalizeSpaces ref, (removeLeadingTrailingSpace src, ""))
475+
476+regularKey = try $ do
477+ string ".. _"
478+ ref <- manyTill inline (char ':')
479+ src <- targetURI
480+ return (normalizeSpaces ref, (removeLeadingTrailingSpace src, ""))
481+
482+ --
483+ -- inline
484+ --
485+
486+inline = choice [ link
487+ , str
488+ , whitespace
489+ , endline
490+ , strong
491+ , emph
492+ , code
493+ , image
494+ , hyphens
495+ , superscript
496+ , subscript
497+ , escapedChar
498+ , symbol ] <?> "inline"
499+
500+hyphens = do
501+ result <- many1 (char '-')
502+ option Space endline
503+ -- don't want to treat endline after hyphen or dash as a space
504+ return $ Str result
505+
506+escapedChar = escaped anyChar
507+
508+symbol = do
509+ result <- oneOf specialChars
510+ return $ Str [result]
511+
512+-- parses inline code, between codeStart and codeEnd
513+code = try $ do
514+ string "``"
515+ result <- manyTill anyChar (try (string "``"))
516+ return $ Code $ removeLeadingTrailingSpace $ joinWithSep " " $ lines result
517+
518+emph = enclosed (char '*') (char '*') inline >>=
519+ return . Emph . normalizeSpaces
520+
521+strong = enclosed (string "**") (try $ string "**") inline >>=
522+ return . Strong . normalizeSpaces
523+
524+interpreted role = try $ do
525+ optional $ try $ string "\\ "
526+ result <- enclosed (string $ ":" ++ role ++ ":`") (char '`') anyChar
527+ nextChar <- lookAhead anyChar
528+ try (string "\\ ") <|> lookAhead (count 1 $ oneOf " \t\n") <|> (eof >> return "")
529+ return [Str result]
530+
531+superscript = interpreted "sup" >>= (return . Superscript)
532+
533+subscript = interpreted "sub" >>= (return . Subscript)
534+
535+whitespace = many1 spaceChar >> return Space <?> "whitespace"
536+
537+str = notFollowedBy' oneWordReference >>
538+ many1 (noneOf (specialChars ++ "\t\n ")) >>= return . Str
539+
540+-- an endline character that can be treated as a space, not a structural break
541+endline = try $ do
542+ newline
543+ notFollowedBy blankline
544+ -- parse potential list-starts at beginning of line differently in a list:
545+ st <- getState
546+ if (stateParserContext st) == ListItemState
547+ then notFollowedBy (anyOrderedListMarker >> spaceChar) >>
548+ notFollowedBy' bulletListStart
549+ else return ()
550+ return Space
551+
552+--
553+-- links
554+--
555+
556+link = choice [explicitLink, referenceLink, autoLink] <?> "link"
557+
558+explicitLink = try $ do
559+ char '`'
560+ notFollowedBy (char '`') -- `` is marks start of inline code
561+ label <- manyTill inline (try (do {spaces; char '<'}))
562+ src <- manyTill (noneOf ">\n ") (char '>')
563+ skipSpaces
564+ string "`_"
565+ return $ Link (normalizeSpaces label) (removeLeadingTrailingSpace src, "")
566+
567+reference = try $ do
568+ char '`'
569+ notFollowedBy (char '`')
570+ label <- many1Till inline (char '`')
571+ char '_'
572+ return label
573+
574+oneWordReference = do
575+ raw <- many1 alphaNum
576+ char '_'
577+ notFollowedBy alphaNum -- because this_is_not a link
578+ return [Str raw]
579+
580+referenceLink = try $ do
581+ label <- reference <|> oneWordReference
582+ key <- option label (do{char '_'; return [Str "_"]}) -- anonymous link
583+ state <- getState
584+ let keyTable = stateKeys state
585+ src <- case lookupKeySrc keyTable key of
586+ Nothing -> fail "no corresponding key"
587+ Just target -> return target
588+ -- if anonymous link, remove first anon key so it won't be used again
589+ let keyTable' = if (key == [Str "_"]) -- anonymous link?
590+ then delete ([Str "_"], src) keyTable -- remove first anon key
591+ else keyTable
592+ setState $ state { stateKeys = keyTable' }
593+ return $ Link (normalizeSpaces label) src
594+
595+uriScheme = oneOfStrings [ "http://", "https://", "ftp://", "file://",
596+ "mailto:", "news:", "telnet:" ]
597+
598+uri = try $ do
599+ scheme <- uriScheme
600+ identifier <- many1 (noneOf " \t\n")
601+ return $ scheme ++ identifier
602+
603+autoURI = do
604+ src <- uri
605+ return $ Link [Str src] (src, "")
606+
607+emailChar = alphaNum <|> oneOf "-+_."
608+
609+emailAddress = try $ do
610+ firstLetter <- alphaNum
611+ restAddr <- many emailChar
612+ let addr = firstLetter:restAddr
613+ char '@'
614+ dom <- domain
615+ return $ addr ++ '@':dom
616+
617+domainChar = alphaNum <|> char '-'
618+
619+domain = do
620+ first <- many1 domainChar
621+ dom <- many1 (try (do{ char '.'; many1 domainChar }))
622+ return $ joinWithSep "." (first:dom)
623+
624+autoEmail = do
625+ src <- emailAddress
626+ return $ Link [Str src] ("mailto:" ++ src, "")
627+
628+autoLink = autoURI <|> autoEmail
629+
630+-- For now, we assume that all substitution references are for images.
631+image = try $ do
632+ char '|'
633+ ref <- manyTill inline (char '|')
634+ state <- getState
635+ let keyTable = stateKeys state
636+ src <- case lookupKeySrc keyTable ref of
637+ Nothing -> fail "no corresponding key"
638+ Just target -> return target
639+ return $ Image (normalizeSpaces ref) src
640+
src/Text/Pandoc/Shared.hsnew file+792
@@ -0,0 +1,792 @@
1+{-
2+Copyright (C) 2006-7 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.Shared
21+ Copyright : Copyright (C) 2006-7 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+Utility functions and definitions used by the various Pandoc modules.
29+-}
30+module Text.Pandoc.Shared (
31+ -- * List processing
32+ splitBy,
33+ splitByIndices,
34+ substitute,
35+ joinWithSep,
36+ -- * Text processing
37+ backslashEscapes,
38+ escapeStringUsing,
39+ stripTrailingNewlines,
40+ removeLeadingTrailingSpace,
41+ removeLeadingSpace,
42+ removeTrailingSpace,
43+ stripFirstAndLast,
44+ camelCaseToHyphenated,
45+ toRomanNumeral,
46+ wrapped,
47+ wrapIfNeeded,
48+ -- * Parsing
49+ (>>~),
50+ anyLine,
51+ many1Till,
52+ notFollowedBy',
53+ oneOfStrings,
54+ spaceChar,
55+ skipSpaces,
56+ blankline,
57+ blanklines,
58+ enclosed,
59+ stringAnyCase,
60+ parseFromString,
61+ lineClump,
62+ charsInBalanced,
63+ charsInBalanced',
64+ romanNumeral,
65+ withHorizDisplacement,
66+ nullBlock,
67+ failIfStrict,
68+ escaped,
69+ anyOrderedListMarker,
70+ orderedListMarker,
71+ charRef,
72+ readWith,
73+ testStringWith,
74+ ParserState (..),
75+ defaultParserState,
76+ HeaderType (..),
77+ ParserContext (..),
78+ QuoteContext (..),
79+ NoteTable,
80+ KeyTable,
81+ lookupKeySrc,
82+ refsMatch,
83+ -- * Native format prettyprinting
84+ prettyPandoc,
85+ -- * Pandoc block and inline list processing
86+ orderedListMarkers,
87+ normalizeSpaces,
88+ compactify,
89+ Element (..),
90+ hierarchicalize,
91+ isHeaderBlock,
92+ -- * Writer options
93+ WriterOptions (..),
94+ defaultWriterOptions
95+ ) where
96+
97+import Text.Pandoc.Definition
98+import Text.ParserCombinators.Parsec
99+import Text.PrettyPrint.HughesPJ ( Doc, fsep )
100+import Text.Pandoc.CharacterReferences ( characterReference )
101+import Data.Char ( toLower, toUpper, ord, isLower, isUpper )
102+import Data.List ( find, isPrefixOf )
103+import Control.Monad ( join )
104+
105+--
106+-- List processing
107+--
108+
109+-- | Split list by groups of one or more sep.
110+splitBy :: (Eq a) => a -> [a] -> [[a]]
111+splitBy _ [] = []
112+splitBy sep lst =
113+ let (first, rest) = break (== sep) lst
114+ rest' = dropWhile (== sep) rest
115+ in first:(splitBy sep rest')
116+
117+-- | Split list into chunks divided at specified indices.
118+splitByIndices :: [Int] -> [a] -> [[a]]
119+splitByIndices [] lst = [lst]
120+splitByIndices (x:xs) lst =
121+ let (first, rest) = splitAt x lst in
122+ first:(splitByIndices (map (\y -> y - x) xs) rest)
123+
124+-- | Replace each occurrence of one sublist in a list with another.
125+substitute :: (Eq a) => [a] -> [a] -> [a] -> [a]
126+substitute _ _ [] = []
127+substitute [] _ lst = lst
128+substitute target replacement lst =
129+ if target `isPrefixOf` lst
130+ then replacement ++ (substitute target replacement $ drop (length target) lst)
131+ else (head lst):(substitute target replacement $ tail lst)
132+
133+-- | Joins a list of lists, separated by another list.
134+joinWithSep :: [a] -- ^ List to use as separator
135+ -> [[a]] -- ^ Lists to join
136+ -> [a]
137+joinWithSep _ [] = []
138+joinWithSep sep lst = foldr1 (\a b -> a ++ sep ++ b) lst
139+
140+--
141+-- Text processing
142+--
143+
144+-- | Returns an association list of backslash escapes for the
145+-- designated characters.
146+backslashEscapes :: [Char] -- ^ list of special characters to escape
147+ -> [(Char, String)]
148+backslashEscapes = map (\ch -> (ch, ['\\',ch]))
149+
150+-- | Escape a string of characters, using an association list of
151+-- characters and strings.
152+escapeStringUsing :: [(Char, String)] -> String -> String
153+escapeStringUsing _ [] = ""
154+escapeStringUsing escapeTable (x:xs) =
155+ case (lookup x escapeTable) of
156+ Just str -> str ++ rest
157+ Nothing -> x:rest
158+ where rest = escapeStringUsing escapeTable xs
159+
160+-- | Strip trailing newlines from string.
161+stripTrailingNewlines :: String -> String
162+stripTrailingNewlines = reverse . dropWhile (== '\n') . reverse
163+
164+-- | Remove leading and trailing space (including newlines) from string.
165+removeLeadingTrailingSpace :: String -> String
166+removeLeadingTrailingSpace = removeLeadingSpace . removeTrailingSpace
167+
168+-- | Remove leading space (including newlines) from string.
169+removeLeadingSpace :: String -> String
170+removeLeadingSpace = dropWhile (`elem` " \n\t")
171+
172+-- | Remove trailing space (including newlines) from string.
173+removeTrailingSpace :: String -> String
174+removeTrailingSpace = reverse . removeLeadingSpace . reverse
175+
176+-- | Strip leading and trailing characters from string
177+stripFirstAndLast :: String -> String
178+stripFirstAndLast str =
179+ drop 1 $ take ((length str) - 1) str
180+
181+-- | Change CamelCase word to hyphenated lowercase (e.g., camel-case).
182+camelCaseToHyphenated :: String -> String
183+camelCaseToHyphenated [] = ""
184+camelCaseToHyphenated (a:b:rest) | isLower a && isUpper b =
185+ a:'-':(toLower b):(camelCaseToHyphenated rest)
186+camelCaseToHyphenated (a:rest) = (toLower a):(camelCaseToHyphenated rest)
187+
188+-- | Convert number < 4000 to uppercase roman numeral.
189+toRomanNumeral :: Int -> String
190+toRomanNumeral x =
191+ if x >= 4000 || x < 0
192+ then "?"
193+ else case x of
194+ _ | x >= 1000 -> "M" ++ toRomanNumeral (x - 1000)
195+ _ | x >= 900 -> "CM" ++ toRomanNumeral (x - 900)
196+ _ | x >= 500 -> "D" ++ toRomanNumeral (x - 500)
197+ _ | x >= 400 -> "CD" ++ toRomanNumeral (x - 400)
198+ _ | x >= 100 -> "C" ++ toRomanNumeral (x - 100)
199+ _ | x >= 90 -> "XC" ++ toRomanNumeral (x - 90)
200+ _ | x >= 50 -> "L" ++ toRomanNumeral (x - 50)
201+ _ | x >= 40 -> "XL" ++ toRomanNumeral (x - 40)
202+ _ | x >= 10 -> "X" ++ toRomanNumeral (x - 10)
203+ _ | x >= 9 -> "IX" ++ toRomanNumeral (x - 5)
204+ _ | x >= 5 -> "V" ++ toRomanNumeral (x - 5)
205+ _ | x >= 4 -> "IV" ++ toRomanNumeral (x - 4)
206+ _ | x >= 1 -> "I" ++ toRomanNumeral (x - 1)
207+ _ -> ""
208+
209+-- | Wrap inlines to line length.
210+wrapped :: Monad m => ([Inline] -> m Doc) -> [Inline] -> m Doc
211+wrapped listWriter sect = (mapM listWriter $ splitBy Space sect) >>=
212+ return . fsep
213+
214+wrapIfNeeded :: Monad m => WriterOptions -> ([Inline] -> m Doc) ->
215+ [Inline] -> m Doc
216+wrapIfNeeded opts = if writerWrapText opts
217+ then wrapped
218+ else ($)
219+
220+--
221+-- Parsing
222+--
223+
224+-- | Like >>, but returns the operation on the left.
225+-- (Suggested by Tillmann Rendel on Haskell-cafe list.)
226+(>>~) :: (Monad m) => m a -> m b -> m a
227+a >>~ b = a >>= \x -> b >> return x
228+
229+-- | Parse any line of text
230+anyLine :: GenParser Char st [Char]
231+anyLine = manyTill anyChar newline
232+
233+-- | Like @manyTill@, but reads at least one item.
234+many1Till :: GenParser tok st a
235+ -> GenParser tok st end
236+ -> GenParser tok st [a]
237+many1Till p end = do
238+ first <- p
239+ rest <- manyTill p end
240+ return (first:rest)
241+
242+-- | A more general form of @notFollowedBy@. This one allows any
243+-- type of parser to be specified, and succeeds only if that parser fails.
244+-- It does not consume any input.
245+notFollowedBy' :: Show b => GenParser a st b -> GenParser a st ()
246+notFollowedBy' p = try $ join $ do a <- try p
247+ return (unexpected (show a))
248+ <|>
249+ return (return ())
250+-- (This version due to Andrew Pimlott on the Haskell mailing list.)
251+
252+-- | Parses one of a list of strings (tried in order).
253+oneOfStrings :: [String] -> GenParser Char st String
254+oneOfStrings listOfStrings = choice $ map (try . string) listOfStrings
255+
256+-- | Parses a space or tab.
257+spaceChar :: CharParser st Char
258+spaceChar = char ' ' <|> char '\t'
259+
260+-- | Skips zero or more spaces or tabs.
261+skipSpaces :: GenParser Char st ()
262+skipSpaces = skipMany spaceChar
263+
264+-- | Skips zero or more spaces or tabs, then reads a newline.
265+blankline :: GenParser Char st Char
266+blankline = try $ skipSpaces >> newline
267+
268+-- | Parses one or more blank lines and returns a string of newlines.
269+blanklines :: GenParser Char st [Char]
270+blanklines = many1 blankline
271+
272+-- | Parses material enclosed between start and end parsers.
273+enclosed :: GenParser Char st t -- ^ start parser
274+ -> GenParser Char st end -- ^ end parser
275+ -> GenParser Char st a -- ^ content parser (to be used repeatedly)
276+ -> GenParser Char st [a]
277+enclosed start end parser = try $
278+ start >> notFollowedBy space >> many1Till parser end
279+
280+-- | Parse string, case insensitive.
281+stringAnyCase :: [Char] -> CharParser st String
282+stringAnyCase [] = string ""
283+stringAnyCase (x:xs) = do
284+ firstChar <- char (toUpper x) <|> char (toLower x)
285+ rest <- stringAnyCase xs
286+ return (firstChar:rest)
287+
288+-- | Parse contents of 'str' using 'parser' and return result.
289+parseFromString :: GenParser tok st a -> [tok] -> GenParser tok st a
290+parseFromString parser str = do
291+ oldPos <- getPosition
292+ oldInput <- getInput
293+ setInput str
294+ result <- parser
295+ setInput oldInput
296+ setPosition oldPos
297+ return result
298+
299+-- | Parse raw line block up to and including blank lines.
300+lineClump :: GenParser Char st String
301+lineClump = blanklines
302+ <|> (many1 (notFollowedBy blankline >> anyLine) >>= return . unlines)
303+
304+-- | Parse a string of characters between an open character
305+-- and a close character, including text between balanced
306+-- pairs of open and close, which must be different. For example,
307+-- @charsInBalanced '(' ')'@ will parse "(hello (there))"
308+-- and return "hello (there)". Stop if a blank line is
309+-- encountered.
310+charsInBalanced :: Char -> Char -> GenParser Char st String
311+charsInBalanced open close = try $ do
312+ char open
313+ raw <- many $ (many1 (noneOf [open, close, '\n']))
314+ <|> (do res <- charsInBalanced open close
315+ return $ [open] ++ res ++ [close])
316+ <|> try (string "\n" >>~ notFollowedBy' blanklines)
317+ char close
318+ return $ concat raw
319+
320+-- | Like @charsInBalanced@, but allow blank lines in the content.
321+charsInBalanced' :: Char -> Char -> GenParser Char st String
322+charsInBalanced' open close = try $ do
323+ char open
324+ raw <- many $ (many1 (noneOf [open, close]))
325+ <|> (do res <- charsInBalanced' open close
326+ return $ [open] ++ res ++ [close])
327+ char close
328+ return $ concat raw
329+
330+-- | Parses a roman numeral (uppercase or lowercase), returns number.
331+romanNumeral :: Bool -- ^ Uppercase if true
332+ -> GenParser Char st Int
333+romanNumeral upperCase = do
334+ let charAnyCase c = char (if upperCase then toUpper c else c)
335+ let one = charAnyCase 'i'
336+ let five = charAnyCase 'v'
337+ let ten = charAnyCase 'x'
338+ let fifty = charAnyCase 'l'
339+ let hundred = charAnyCase 'c'
340+ let fivehundred = charAnyCase 'd'
341+ let thousand = charAnyCase 'm'
342+ thousands <- many thousand >>= (return . (1000 *) . length)
343+ ninehundreds <- option 0 $ try $ hundred >> thousand >> return 900
344+ fivehundreds <- many fivehundred >>= (return . (500 *) . length)
345+ fourhundreds <- option 0 $ try $ hundred >> fivehundred >> return 400
346+ hundreds <- many hundred >>= (return . (100 *) . length)
347+ nineties <- option 0 $ try $ ten >> hundred >> return 90
348+ fifties <- many fifty >>= (return . (50 *) . length)
349+ forties <- option 0 $ try $ ten >> fifty >> return 40
350+ tens <- many ten >>= (return . (10 *) . length)
351+ nines <- option 0 $ try $ one >> ten >> return 9
352+ fives <- many five >>= (return . (5 *) . length)
353+ fours <- option 0 $ try $ one >> five >> return 4
354+ ones <- many one >>= (return . length)
355+ let total = thousands + ninehundreds + fivehundreds + fourhundreds +
356+ hundreds + nineties + fifties + forties + tens + nines +
357+ fives + fours + ones
358+ if total == 0
359+ then fail "not a roman numeral"
360+ else return total
361+
362+-- | Applies a parser, returns tuple of its results and its horizontal
363+-- displacement (the difference between the source column at the end
364+-- and the source column at the beginning). Vertical displacement
365+-- (source row) is ignored.
366+withHorizDisplacement :: GenParser Char st a -- ^ Parser to apply
367+ -> GenParser Char st (a, Int) -- ^ (result, displacement)
368+withHorizDisplacement parser = do
369+ pos1 <- getPosition
370+ result <- parser
371+ pos2 <- getPosition
372+ return (result, sourceColumn pos2 - sourceColumn pos1)
373+
374+-- | Parses a character and returns 'Null' (so that the parser can move on
375+-- if it gets stuck).
376+nullBlock :: GenParser Char st Block
377+nullBlock = anyChar >> return Null
378+
379+-- | Fail if reader is in strict markdown syntax mode.
380+failIfStrict :: GenParser Char ParserState ()
381+failIfStrict = do
382+ state <- getState
383+ if stateStrict state then fail "strict mode" else return ()
384+
385+-- | Parses backslash, then applies character parser.
386+escaped :: GenParser Char st Char -- ^ Parser for character to escape
387+ -> GenParser Char st Inline
388+escaped parser = try $ do
389+ char '\\'
390+ result <- parser
391+ return (Str [result])
392+
393+-- | Parses an uppercase roman numeral and returns (UpperRoman, number).
394+upperRoman :: GenParser Char st (ListNumberStyle, Int)
395+upperRoman = do
396+ num <- romanNumeral True
397+ return (UpperRoman, num)
398+
399+-- | Parses a lowercase roman numeral and returns (LowerRoman, number).
400+lowerRoman :: GenParser Char st (ListNumberStyle, Int)
401+lowerRoman = do
402+ num <- romanNumeral False
403+ return (LowerRoman, num)
404+
405+-- | Parses a decimal numeral and returns (Decimal, number).
406+decimal :: GenParser Char st (ListNumberStyle, Int)
407+decimal = do
408+ num <- many1 digit
409+ return (Decimal, read num)
410+
411+-- | Parses a '#' returns (DefaultStyle, 1).
412+defaultNum :: GenParser Char st (ListNumberStyle, Int)
413+defaultNum = do
414+ char '#'
415+ return (DefaultStyle, 1)
416+
417+-- | Parses a lowercase letter and returns (LowerAlpha, number).
418+lowerAlpha :: GenParser Char st (ListNumberStyle, Int)
419+lowerAlpha = do
420+ ch <- oneOf ['a'..'z']
421+ return (LowerAlpha, ord ch - ord 'a' + 1)
422+
423+-- | Parses an uppercase letter and returns (UpperAlpha, number).
424+upperAlpha :: GenParser Char st (ListNumberStyle, Int)
425+upperAlpha = do
426+ ch <- oneOf ['A'..'Z']
427+ return (UpperAlpha, ord ch - ord 'A' + 1)
428+
429+-- | Parses a roman numeral i or I
430+romanOne :: GenParser Char st (ListNumberStyle, Int)
431+romanOne = (char 'i' >> return (LowerRoman, 1)) <|>
432+ (char 'I' >> return (UpperRoman, 1))
433+
434+-- | Parses an ordered list marker and returns list attributes.
435+anyOrderedListMarker :: GenParser Char st ListAttributes
436+anyOrderedListMarker = choice $
437+ [delimParser numParser | delimParser <- [inPeriod, inOneParen, inTwoParens],
438+ numParser <- [decimal, defaultNum, romanOne,
439+ lowerAlpha, lowerRoman, upperAlpha, upperRoman]]
440+
441+-- | Parses a list number (num) followed by a period, returns list attributes.
442+inPeriod :: GenParser Char st (ListNumberStyle, Int)
443+ -> GenParser Char st ListAttributes
444+inPeriod num = try $ do
445+ (style, start) <- num
446+ char '.'
447+ let delim = if style == DefaultStyle
448+ then DefaultDelim
449+ else Period
450+ return (start, style, delim)
451+
452+-- | Parses a list number (num) followed by a paren, returns list attributes.
453+inOneParen :: GenParser Char st (ListNumberStyle, Int)
454+ -> GenParser Char st ListAttributes
455+inOneParen num = try $ do
456+ (style, start) <- num
457+ char ')'
458+ return (start, style, OneParen)
459+
460+-- | Parses a list number (num) enclosed in parens, returns list attributes.
461+inTwoParens :: GenParser Char st (ListNumberStyle, Int)
462+ -> GenParser Char st ListAttributes
463+inTwoParens num = try $ do
464+ char '('
465+ (style, start) <- num
466+ char ')'
467+ return (start, style, TwoParens)
468+
469+-- | Parses an ordered list marker with a given style and delimiter,
470+-- returns number.
471+orderedListMarker :: ListNumberStyle
472+ -> ListNumberDelim
473+ -> GenParser Char st Int
474+orderedListMarker style delim = do
475+ let num = case style of
476+ DefaultStyle -> decimal <|> defaultNum
477+ Decimal -> decimal
478+ UpperRoman -> upperRoman
479+ LowerRoman -> lowerRoman
480+ UpperAlpha -> upperAlpha
481+ LowerAlpha -> lowerAlpha
482+ let context = case delim of
483+ DefaultDelim -> inPeriod
484+ Period -> inPeriod
485+ OneParen -> inOneParen
486+ TwoParens -> inTwoParens
487+ (start, _, _) <- context num
488+ return start
489+
490+-- | Parses a character reference and returns a Str element.
491+charRef :: GenParser Char st Inline
492+charRef = do
493+ c <- characterReference
494+ return $ Str [c]
495+
496+-- | Parse a string with a given parser and state.
497+readWith :: GenParser Char ParserState a -- ^ parser
498+ -> ParserState -- ^ initial state
499+ -> String -- ^ input string
500+ -> a
501+readWith parser state input =
502+ case runParser parser state "source" input of
503+ Left err -> error $ "\nError:\n" ++ show err
504+ Right result -> result
505+
506+-- | Parse a string with @parser@ (for testing).
507+testStringWith :: (Show a) => GenParser Char ParserState a
508+ -> String
509+ -> IO ()
510+testStringWith parser str = putStrLn $ show $
511+ readWith parser defaultParserState str
512+
513+-- | Parsing options.
514+data ParserState = ParserState
515+ { stateParseRaw :: Bool, -- ^ Parse raw HTML and LaTeX?
516+ stateParserContext :: ParserContext, -- ^ Inside list?
517+ stateQuoteContext :: QuoteContext, -- ^ Inside quoted environment?
518+ stateKeys :: KeyTable, -- ^ List of reference keys
519+ stateNotes :: NoteTable, -- ^ List of notes
520+ stateTabStop :: Int, -- ^ Tab stop
521+ stateStandalone :: Bool, -- ^ Parse bibliographic info?
522+ stateTitle :: [Inline], -- ^ Title of document
523+ stateAuthors :: [String], -- ^ Authors of document
524+ stateDate :: String, -- ^ Date of document
525+ stateStrict :: Bool, -- ^ Use strict markdown syntax?
526+ stateSmart :: Bool, -- ^ Use smart typography?
527+ stateColumns :: Int, -- ^ Number of columns in terminal
528+ stateHeaderTable :: [HeaderType] -- ^ Ordered list of header types used
529+ }
530+ deriving Show
531+
532+defaultParserState :: ParserState
533+defaultParserState =
534+ ParserState { stateParseRaw = False,
535+ stateParserContext = NullState,
536+ stateQuoteContext = NoQuote,
537+ stateKeys = [],
538+ stateNotes = [],
539+ stateTabStop = 4,
540+ stateStandalone = False,
541+ stateTitle = [],
542+ stateAuthors = [],
543+ stateDate = [],
544+ stateStrict = False,
545+ stateSmart = False,
546+ stateColumns = 80,
547+ stateHeaderTable = [] }
548+
549+data HeaderType
550+ = SingleHeader Char -- ^ Single line of characters underneath
551+ | DoubleHeader Char -- ^ Lines of characters above and below
552+ deriving (Eq, Show)
553+
554+data ParserContext
555+ = ListItemState -- ^ Used when running parser on list item contents
556+ | NullState -- ^ Default state
557+ deriving (Eq, Show)
558+
559+data QuoteContext
560+ = InSingleQuote -- ^ Used when parsing inside single quotes
561+ | InDoubleQuote -- ^ Used when parsing inside double quotes
562+ | NoQuote -- ^ Used when not parsing inside quotes
563+ deriving (Eq, Show)
564+
565+type NoteTable = [(String, [Block])]
566+
567+type KeyTable = [([Inline], Target)]
568+
569+-- | Look up key in key table and return target object.
570+lookupKeySrc :: KeyTable -- ^ Key table
571+ -> [Inline] -- ^ Key
572+ -> Maybe Target
573+lookupKeySrc table key = case find (refsMatch key . fst) table of
574+ Nothing -> Nothing
575+ Just (_, src) -> Just src
576+
577+-- | Returns @True@ if keys match (case insensitive).
578+refsMatch :: [Inline] -> [Inline] -> Bool
579+refsMatch ((Str x):restx) ((Str y):resty) =
580+ ((map toLower x) == (map toLower y)) && refsMatch restx resty
581+refsMatch ((Emph x):restx) ((Emph y):resty) =
582+ refsMatch x y && refsMatch restx resty
583+refsMatch ((Strong x):restx) ((Strong y):resty) =
584+ refsMatch x y && refsMatch restx resty
585+refsMatch ((Strikeout x):restx) ((Strikeout y):resty) =
586+ refsMatch x y && refsMatch restx resty
587+refsMatch ((Superscript x):restx) ((Superscript y):resty) =
588+ refsMatch x y && refsMatch restx resty
589+refsMatch ((Subscript x):restx) ((Subscript y):resty) =
590+ refsMatch x y && refsMatch restx resty
591+refsMatch ((Quoted t x):restx) ((Quoted u y):resty) =
592+ t == u && refsMatch x y && refsMatch restx resty
593+refsMatch ((Code x):restx) ((Code y):resty) =
594+ ((map toLower x) == (map toLower y)) && refsMatch restx resty
595+refsMatch ((TeX x):restx) ((TeX y):resty) =
596+ ((map toLower x) == (map toLower y)) && refsMatch restx resty
597+refsMatch ((HtmlInline x):restx) ((HtmlInline y):resty) =
598+ ((map toLower x) == (map toLower y)) && refsMatch restx resty
599+refsMatch (x:restx) (y:resty) = (x == y) && refsMatch restx resty
600+refsMatch [] x = null x
601+refsMatch x [] = null x
602+
603+--
604+-- Native format prettyprinting
605+--
606+
607+-- | Indent string as a block.
608+indentBy :: Int -- ^ Number of spaces to indent the block
609+ -> Int -- ^ Number of spaces (rel to block) to indent first line
610+ -> String -- ^ Contents of block to indent
611+ -> String
612+indentBy _ _ [] = ""
613+indentBy num first str =
614+ let (firstLine:restLines) = lines str
615+ firstLineIndent = num + first
616+ in (replicate firstLineIndent ' ') ++ firstLine ++ "\n" ++
617+ (joinWithSep "\n" $ map ((replicate num ' ') ++ ) restLines)
618+
619+-- | Prettyprint list of Pandoc blocks elements.
620+prettyBlockList :: Int -- ^ Number of spaces to indent list of blocks
621+ -> [Block] -- ^ List of blocks
622+ -> String
623+prettyBlockList indent [] = indentBy indent 0 "[]"
624+prettyBlockList indent blocks = indentBy indent (-2) $ "[ " ++
625+ (joinWithSep "\n, " (map prettyBlock blocks)) ++ " ]"
626+
627+-- | Prettyprint Pandoc block element.
628+prettyBlock :: Block -> String
629+prettyBlock (BlockQuote blocks) = "BlockQuote\n " ++
630+ (prettyBlockList 2 blocks)
631+prettyBlock (OrderedList attribs blockLists) =
632+ "OrderedList " ++ show attribs ++ "\n" ++ indentBy 2 0 ("[ " ++
633+ (joinWithSep ", " $ map (\blocks -> prettyBlockList 2 blocks)
634+ blockLists)) ++ " ]"
635+prettyBlock (BulletList blockLists) = "BulletList\n" ++
636+ indentBy 2 0 ("[ " ++ (joinWithSep ", "
637+ (map (\blocks -> prettyBlockList 2 blocks) blockLists))) ++ " ]"
638+prettyBlock (DefinitionList blockLists) = "DefinitionList\n" ++
639+ indentBy 2 0 ("[" ++ (joinWithSep ",\n"
640+ (map (\(term, blocks) -> " (" ++ show term ++ ",\n" ++
641+ indentBy 1 2 (prettyBlockList 2 blocks) ++ " )") blockLists))) ++ " ]"
642+prettyBlock (Table caption aligns widths header rows) =
643+ "Table " ++ show caption ++ " " ++ show aligns ++ " " ++
644+ show widths ++ "\n" ++ prettyRow header ++ " [\n" ++
645+ (joinWithSep ",\n" (map prettyRow rows)) ++ " ]"
646+ where prettyRow cols = indentBy 2 0 ("[ " ++ (joinWithSep ", "
647+ (map (\blocks -> prettyBlockList 2 blocks)
648+ cols))) ++ " ]"
649+prettyBlock block = show block
650+
651+-- | Prettyprint Pandoc document.
652+prettyPandoc :: Pandoc -> String
653+prettyPandoc (Pandoc meta blocks) = "Pandoc " ++ "(" ++ show meta ++
654+ ")\n" ++ (prettyBlockList 0 blocks) ++ "\n"
655+
656+--
657+-- Pandoc block and inline list processing
658+--
659+
660+-- | Generate infinite lazy list of markers for an ordered list,
661+-- depending on list attributes.
662+orderedListMarkers :: (Int, ListNumberStyle, ListNumberDelim) -> [String]
663+orderedListMarkers (start, numstyle, numdelim) =
664+ let singleton c = [c]
665+ nums = case numstyle of
666+ DefaultStyle -> map show [start..]
667+ Decimal -> map show [start..]
668+ UpperAlpha -> drop (start - 1) $ cycle $
669+ map singleton ['A'..'Z']
670+ LowerAlpha -> drop (start - 1) $ cycle $
671+ map singleton ['a'..'z']
672+ UpperRoman -> map toRomanNumeral [start..]
673+ LowerRoman -> map (map toLower . toRomanNumeral) [start..]
674+ inDelim str = case numdelim of
675+ DefaultDelim -> str ++ "."
676+ Period -> str ++ "."
677+ OneParen -> str ++ ")"
678+ TwoParens -> "(" ++ str ++ ")"
679+ in map inDelim nums
680+
681+-- | Normalize a list of inline elements: remove leading and trailing
682+-- @Space@ elements, collapse double @Space@s into singles, and
683+-- remove empty Str elements.
684+normalizeSpaces :: [Inline] -> [Inline]
685+normalizeSpaces [] = []
686+normalizeSpaces list =
687+ let removeDoubles [] = []
688+ removeDoubles (Space:Space:rest) = removeDoubles (Space:rest)
689+ removeDoubles (Space:(Str ""):Space:rest) = removeDoubles (Space:rest)
690+ removeDoubles ((Str ""):rest) = removeDoubles rest
691+ removeDoubles (x:rest) = x:(removeDoubles rest)
692+ removeLeading (Space:xs) = removeLeading xs
693+ removeLeading x = x
694+ removeTrailing [] = []
695+ removeTrailing lst = if (last lst == Space)
696+ then init lst
697+ else lst
698+ in removeLeading $ removeTrailing $ removeDoubles list
699+
700+-- | Change final list item from @Para@ to @Plain@ if the list should
701+-- be compact.
702+compactify :: [[Block]] -- ^ List of list items (each a list of blocks)
703+ -> [[Block]]
704+compactify [] = []
705+compactify items =
706+ let final = last items
707+ others = init items
708+ in case final of
709+ [Para a] -> if any containsPara others
710+ then items
711+ else others ++ [[Plain a]]
712+ _ -> items
713+
714+containsPara :: [Block] -> Bool
715+containsPara [] = False
716+containsPara ((Para _):_) = True
717+containsPara ((BulletList items):rest) = any containsPara items ||
718+ containsPara rest
719+containsPara ((OrderedList _ items):rest) = any containsPara items ||
720+ containsPara rest
721+containsPara ((DefinitionList items):rest) = any containsPara (map snd items) ||
722+ containsPara rest
723+containsPara (_:rest) = containsPara rest
724+
725+-- | Data structure for defining hierarchical Pandoc documents
726+data Element = Blk Block
727+ | Sec [Inline] [Element] deriving (Eq, Read, Show)
728+
729+-- | Returns @True@ on Header block with at least the specified level
730+headerAtLeast :: Int -> Block -> Bool
731+headerAtLeast level (Header x _) = x <= level
732+headerAtLeast _ _ = False
733+
734+-- | Convert list of Pandoc blocks into (hierarchical) list of Elements
735+hierarchicalize :: [Block] -> [Element]
736+hierarchicalize [] = []
737+hierarchicalize (block:rest) =
738+ case block of
739+ (Header level title) ->
740+ let (thisSection, rest') = break (headerAtLeast level) rest
741+ in (Sec title (hierarchicalize thisSection)):(hierarchicalize rest')
742+ x -> (Blk x):(hierarchicalize rest)
743+
744+-- | True if block is a Header block.
745+isHeaderBlock :: Block -> Bool
746+isHeaderBlock (Header _ _) = True
747+isHeaderBlock _ = False
748+
749+--
750+-- Writer options
751+--
752+
753+-- | Options for writers
754+data WriterOptions = WriterOptions
755+ { writerStandalone :: Bool -- ^ Include header and footer
756+ , writerHeader :: String -- ^ Header for the document
757+ , writerTitlePrefix :: String -- ^ Prefix for HTML titles
758+ , writerTabStop :: Int -- ^ Tabstop for conversion btw spaces and tabs
759+ , writerTableOfContents :: Bool -- ^ Include table of contents
760+ , writerS5 :: Bool -- ^ We're writing S5
761+ , writerUseASCIIMathML :: Bool -- ^ Use ASCIIMathML
762+ , writerASCIIMathMLURL :: Maybe String -- ^ URL to asciiMathML.js
763+ , writerIgnoreNotes :: Bool -- ^ Ignore footnotes (used in making toc)
764+ , writerIncremental :: Bool -- ^ Incremental S5 lists
765+ , writerNumberSections :: Bool -- ^ Number sections in LaTeX
766+ , writerIncludeBefore :: String -- ^ String to include before the body
767+ , writerIncludeAfter :: String -- ^ String to include after the body
768+ , writerStrictMarkdown :: Bool -- ^ Use strict markdown syntax
769+ , writerReferenceLinks :: Bool -- ^ Use reference links in writing markdown, rst
770+ , writerWrapText :: Bool -- ^ Wrap text to line length
771+ } deriving Show
772+
773+-- | Default writer options.
774+defaultWriterOptions :: WriterOptions
775+defaultWriterOptions =
776+ WriterOptions { writerStandalone = False
777+ , writerHeader = ""
778+ , writerTitlePrefix = ""
779+ , writerTabStop = 4
780+ , writerTableOfContents = False
781+ , writerS5 = False
782+ , writerUseASCIIMathML = False
783+ , writerASCIIMathMLURL = Nothing
784+ , writerIgnoreNotes = False
785+ , writerIncremental = False
786+ , writerNumberSections = False
787+ , writerIncludeBefore = ""
788+ , writerIncludeAfter = ""
789+ , writerStrictMarkdown = False
790+ , writerReferenceLinks = False
791+ , writerWrapText = True
792+ }
src/Text/Pandoc/UTF8.hsnew file+45
@@ -0,0 +1,45 @@
1+-- | Functions for converting Unicode strings to UTF-8 and vice versa.
2+--
3+-- Taken from <http://www.cse.ogi.edu/~hallgren/Talks/LHiH/base/lib/UTF8.hs>.
4+-- (c) 2003, OGI School of Science & Engineering, Oregon Health and
5+-- Science University.
6+--
7+-- Modified by Martin Norbaeck
8+-- to pass illegal UTF-8 sequences through unchanged.
9+module Text.Pandoc.UTF8 (
10+ fromUTF8,
11+ toUTF8
12+ ) where
13+
14+-- From the Char module supplied with HBC.
15+
16+-- | Take a UTF-8 string and decode it into a Unicode string.
17+fromUTF8 :: String -> String
18+fromUTF8 "" = ""
19+fromUTF8 ('\xef':'\xbb':'\xbf':cs) = fromUTF8 cs -- skip BOM (byte order marker)
20+fromUTF8 (c:c':cs) | '\xc0' <= c && c <= '\xdf' &&
21+ '\x80' <= c' && c' <= '\xbf' =
22+ toEnum ((fromEnum c `mod` 0x20) * 0x40 + fromEnum c' `mod` 0x40) : fromUTF8 cs
23+fromUTF8 (c:c':c'':cs) | '\xe0' <= c && c <= '\xef' &&
24+ '\x80' <= c' && c' <= '\xbf' &&
25+ '\x80' <= c'' && c'' <= '\xbf' =
26+ toEnum ((fromEnum c `mod` 0x10 * 0x1000) + (fromEnum c' `mod` 0x40) * 0x40 + fromEnum c'' `mod` 0x40) : fromUTF8 cs
27+fromUTF8 (c:cs) = c : fromUTF8 cs
28+
29+-- | Take a Unicode string and encode it as a UTF-8 string.
30+toUTF8 :: String -> String
31+toUTF8 "" = ""
32+toUTF8 (c:cs) =
33+ if c > '\x0000' && c < '\x0080' then
34+ c : toUTF8 cs
35+ else if c < toEnum 0x0800 then
36+ let i = fromEnum c
37+ in toEnum (0xc0 + i `div` 0x40) :
38+ toEnum (0x80 + i `mod` 0x40) :
39+ toUTF8 cs
40+ else
41+ let i = fromEnum c
42+ in toEnum (0xe0 + i `div` 0x1000) :
43+ toEnum (0x80 + (i `mod` 0x1000) `div` 0x40) :
44+ toEnum (0x80 + i `mod` 0x40) :
45+ toUTF8 cs
src/Text/Pandoc/Writers/ConTeXt.hsnew file+248
@@ -0,0 +1,248 @@
1+{-
2+Copyright (C) 2007 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.ConTeXt
21+ Copyright : Copyright (C) 2007 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' format into ConTeXt.
29+-}
30+module Text.Pandoc.Writers.ConTeXt ( writeConTeXt ) where
31+import Text.Pandoc.Definition
32+import Text.Pandoc.Shared
33+import Text.Printf ( printf )
34+import Data.List ( (\\), intersperse )
35+import Control.Monad.State
36+
37+type WriterState = Int -- number of next URL reference
38+
39+-- | Convert Pandoc to ConTeXt.
40+writeConTeXt :: WriterOptions -> Pandoc -> String
41+writeConTeXt options document = evalState (pandocToConTeXt options document) 1
42+
43+pandocToConTeXt :: WriterOptions -> Pandoc -> State WriterState String
44+pandocToConTeXt options (Pandoc meta blocks) = do
45+ main <- blockListToConTeXt blocks
46+ let body = writerIncludeBefore options ++ main ++ writerIncludeAfter options
47+ head <- if writerStandalone options
48+ then contextHeader options meta
49+ else return ""
50+ let toc = if writerTableOfContents options
51+ then "\\placecontent\n\n"
52+ else ""
53+ let foot = if writerStandalone options
54+ then "\n\\stoptext\n"
55+ else ""
56+ return $ head ++ toc ++ body ++ foot
57+
58+-- | Insert bibliographic information into ConTeXt header.
59+contextHeader :: WriterOptions -- ^ Options, including ConTeXt header
60+ -> Meta -- ^ Meta with bibliographic information
61+ -> State WriterState String
62+contextHeader options (Meta title authors date) = do
63+ titletext <- if null title
64+ then return ""
65+ else inlineListToConTeXt title
66+ let authorstext = if null authors
67+ then ""
68+ else if length authors == 1
69+ then stringToConTeXt $ head authors
70+ else stringToConTeXt $ (joinWithSep ", " $
71+ init authors) ++ " & " ++ last authors
72+ let datetext = if date == ""
73+ then ""
74+ else stringToConTeXt date
75+ let titleblock = "\\doctitle{" ++ titletext ++ "}\n\
76+ \ \\author{" ++ authorstext ++ "}\n\
77+ \ \\date{" ++ datetext ++ "}\n\n"
78+ let setupheads = if (writerNumberSections options)
79+ then "\\setupheads[sectionnumber=yes, style=\\bf]\n"
80+ else "\\setupheads[sectionnumber=no, style=\\bf]\n"
81+ let header = writerHeader options
82+ return $ header ++ setupheads ++ titleblock ++ "\\starttext\n\\maketitle\n\n"
83+
84+-- escape things as needed for ConTeXt
85+
86+escapeCharForConTeXt :: Char -> String
87+escapeCharForConTeXt ch =
88+ case ch of
89+ '{' -> "\\letteropenbrace{}"
90+ '}' -> "\\letterclosebrace{}"
91+ '\\' -> "\\letterbackslash{}"
92+ '$' -> "\\$"
93+ '|' -> "\\letterbar{}"
94+ '^' -> "\\letterhat{}"
95+ '%' -> "\\%"
96+ '~' -> "\\lettertilde{}"
97+ '&' -> "\\&"
98+ '#' -> "\\#"
99+ '<' -> "\\letterless{}"
100+ '>' -> "\\lettermore{}"
101+ '_' -> "\\letterunderscore{}"
102+ x -> [x]
103+
104+-- | Escape string for ConTeXt
105+stringToConTeXt :: String -> String
106+stringToConTeXt = concatMap escapeCharForConTeXt
107+
108+-- | Convert Pandoc block element to ConTeXt.
109+blockToConTeXt :: Block -> State WriterState String
110+blockToConTeXt Null = return ""
111+blockToConTeXt (Plain lst) = inlineListToConTeXt lst >>= return . (++ "\n")
112+blockToConTeXt (Para lst) = inlineListToConTeXt lst >>= return . (++ "\n\n")
113+blockToConTeXt (BlockQuote lst) = do
114+ contents <- blockListToConTeXt lst
115+ return $ "\\startblockquote\n" ++ contents ++ "\\stopblockquote\n\n"
116+blockToConTeXt (CodeBlock str) =
117+ return $ "\\starttyping\n" ++ str ++ "\n\\stoptyping\n"
118+blockToConTeXt (RawHtml str) = return ""
119+blockToConTeXt (BulletList lst) = do
120+ contents <- mapM listItemToConTeXt lst
121+ return $ "\\startltxitem\n" ++ concat contents ++ "\\stopltxitem\n"
122+blockToConTeXt (OrderedList attribs lst) = case attribs of
123+ (1, DefaultStyle, DefaultDelim) -> do
124+ contents <- mapM listItemToConTeXt lst
125+ return $ "\\startltxenum\n" ++ concat contents ++ "\\stopltxenum\n"
126+ _ -> do
127+ let markers = take (length lst) $ orderedListMarkers attribs
128+ contents <- zipWithM orderedListItemToConTeXt markers lst
129+ let markerWidth = maximum $ map length markers
130+ let markerWidth' = if markerWidth < 3
131+ then ""
132+ else "[width=" ++
133+ show ((markerWidth + 2) `div` 2) ++ "em]"
134+ return $ "\\startitemize" ++ markerWidth' ++ "\n" ++ concat contents ++
135+ "\\stopitemize\n"
136+blockToConTeXt (DefinitionList lst) =
137+ mapM defListItemToConTeXt lst >>= return . (++ "\n") . concat
138+blockToConTeXt HorizontalRule = return "\\thinrule\n\n"
139+blockToConTeXt (Header level lst) = do
140+ contents <- inlineListToConTeXt lst
141+ return $ if level > 0 && level <= 3
142+ then "\\" ++ concat (replicate (level - 1) "sub") ++
143+ "section{" ++ contents ++ "}\n\n"
144+ else contents ++ "\n\n"
145+blockToConTeXt (Table caption aligns widths heads rows) = do
146+ let colWidths = map printDecimal widths
147+ let colDescriptor colWidth alignment = (case alignment of
148+ AlignLeft -> 'l'
149+ AlignRight -> 'r'
150+ AlignCenter -> 'c'
151+ AlignDefault -> 'l'):
152+ "p(" ++ colWidth ++ "\\textwidth)|"
153+ let colDescriptors = "|" ++ (concat $
154+ zipWith colDescriptor colWidths aligns)
155+ headers <- tableRowToConTeXt heads
156+ captionText <- inlineListToConTeXt caption
157+ let captionText' = if null caption then "none" else captionText
158+ rows' <- mapM tableRowToConTeXt rows
159+ return $ "\\placetable[here]{" ++ captionText' ++ "}\n\\starttable[" ++
160+ colDescriptors ++ "]\n" ++ "\\HL\n" ++ headers ++ "\\HL\n" ++
161+ concat rows' ++ "\\HL\n\\stoptable\n\n"
162+
163+printDecimal :: Float -> String
164+printDecimal = printf "%.2f"
165+
166+tableRowToConTeXt cols = do
167+ cols' <- mapM blockListToConTeXt cols
168+ return $ "\\NC " ++ (concat $ intersperse "\\NC " cols') ++ "\\NC\\AR\n"
169+
170+listItemToConTeXt list = do
171+ contents <- blockListToConTeXt list
172+ return $ "\\item " ++ contents
173+
174+orderedListItemToConTeXt marker list = do
175+ contents <- blockListToConTeXt list
176+ return $ "\\sym{" ++ marker ++ "} " ++ contents
177+
178+defListItemToConTeXt (term, def) = do
179+ term' <- inlineListToConTeXt term
180+ def' <- blockListToConTeXt def
181+ return $ "\\startdescr{" ++ term' ++ "}\n" ++
182+ def' ++ "\n\\stopdescr\n"
183+
184+-- | Convert list of block elements to ConTeXt.
185+blockListToConTeXt :: [Block] -> State WriterState String
186+blockListToConTeXt lst = mapM blockToConTeXt lst >>= return . concat
187+
188+-- | Convert list of inline elements to ConTeXt.
189+inlineListToConTeXt :: [Inline] -- ^ Inlines to convert
190+ -> State WriterState String
191+inlineListToConTeXt lst = mapM inlineToConTeXt lst >>= return . concat
192+
193+isQuoted :: Inline -> Bool
194+isQuoted (Quoted _ _) = True
195+isQuoted Apostrophe = True
196+isQuoted _ = False
197+
198+-- | Convert inline element to ConTeXt
199+inlineToConTeXt :: Inline -- ^ Inline to convert
200+ -> State WriterState String
201+inlineToConTeXt (Emph lst) = do
202+ contents <- inlineListToConTeXt lst
203+ return $ "{\\em " ++ contents ++ "}"
204+inlineToConTeXt (Strong lst) = do
205+ contents <- inlineListToConTeXt lst
206+ return $ "{\\bf " ++ contents ++ "}"
207+inlineToConTeXt (Strikeout lst) = do
208+ contents <- inlineListToConTeXt lst
209+ return $ "\\overstrikes{" ++ contents ++ "}"
210+inlineToConTeXt (Superscript lst) = do
211+ contents <- inlineListToConTeXt lst
212+ return $ "\\high{" ++ contents ++ "}"
213+inlineToConTeXt (Subscript lst) = do
214+ contents <- inlineListToConTeXt lst
215+ return $ "\\low{" ++ contents ++ "}"
216+inlineToConTeXt (Code str) = return $ "\\type{" ++ str ++ "}"
217+inlineToConTeXt (Quoted SingleQuote lst) = do
218+ contents <- inlineListToConTeXt lst
219+ return $ "\\quote{" ++ contents ++ "}"
220+inlineToConTeXt (Quoted DoubleQuote lst) = do
221+ contents <- inlineListToConTeXt lst
222+ return $ "\\quotation{" ++ contents ++ "}"
223+inlineToConTeXt Apostrophe = return "'"
224+inlineToConTeXt EmDash = return "---"
225+inlineToConTeXt EnDash = return "--"
226+inlineToConTeXt Ellipses = return "\\ldots{}"
227+inlineToConTeXt (Str str) = return $ stringToConTeXt str
228+inlineToConTeXt (TeX str) = return str
229+inlineToConTeXt (HtmlInline str) = return ""
230+inlineToConTeXt (LineBreak) = return "\\crlf\n"
231+inlineToConTeXt Space = return " "
232+inlineToConTeXt (Link [Code str] (src, tit)) = -- since ConTeXt has its own
233+ inlineToConTeXt (Link [Str str] (src, tit)) -- way of printing links...
234+inlineToConTeXt (Link text (src, _)) = do
235+ next <- get
236+ put (next + 1)
237+ let ref = show next
238+ label <- inlineListToConTeXt text
239+ return $ "\\useurl[" ++ ref ++ "][" ++ src ++ "][][" ++ label ++
240+ "]\\from[" ++ ref ++ "]"
241+inlineToConTeXt (Image alternate (src, tit)) = do
242+ alt <- inlineListToConTeXt alternate
243+ return $ "\\placefigure\n[]\n[fig:" ++ alt ++ "]\n{" ++
244+ tit ++ "}\n{\\externalfigure[" ++ src ++ "]}"
245+inlineToConTeXt (Note contents) = do
246+ contents' <- blockListToConTeXt contents
247+ return $ "\\footnote{" ++ contents' ++ "}"
248+
src/Text/Pandoc/Writers/Docbook.hsnew file+299
@@ -0,0 +1,299 @@
1+{-
2+Copyright (C) 2006-7 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.Docbook
21+ Copyright : Copyright (C) 2006-7 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 Docbook XML.
29+-}
30+module Text.Pandoc.Writers.Docbook ( writeDocbook) where
31+import Text.Pandoc.Definition
32+import Text.Pandoc.Shared
33+import Data.List ( isPrefixOf, drop )
34+import Text.PrettyPrint.HughesPJ hiding ( Str )
35+
36+--
37+-- code to format XML
38+--
39+
40+-- | Escape one character as needed for XML.
41+escapeCharForXML :: Char -> String
42+escapeCharForXML x = case x of
43+ '&' -> "&amp;"
44+ '<' -> "&lt;"
45+ '>' -> "&gt;"
46+ '"' -> "&quot;"
47+ '\160' -> "&nbsp;"
48+ c -> [c]
49+
50+-- | True if the character needs to be escaped.
51+needsEscaping :: Char -> Bool
52+needsEscaping c = c `elem` "&<>\"\160"
53+
54+-- | Escape string as needed for XML. Entity references are not preserved.
55+escapeStringForXML :: String -> String
56+escapeStringForXML "" = ""
57+escapeStringForXML str =
58+ case break needsEscaping str of
59+ (okay, "") -> okay
60+ (okay, (c:cs)) -> okay ++ escapeCharForXML c ++ escapeStringForXML cs
61+
62+-- | Return a text object with a string of formatted XML attributes.
63+attributeList :: [(String, String)] -> Doc
64+attributeList = text . concatMap
65+ (\(a, b) -> " " ++ escapeStringForXML a ++ "=\"" ++
66+ escapeStringForXML b ++ "\"")
67+
68+-- | Put the supplied contents between start and end tags of tagType,
69+-- with specified attributes and (if specified) indentation.
70+inTags:: Bool -> String -> [(String, String)] -> Doc -> Doc
71+inTags isIndented tagType attribs contents =
72+ let openTag = char '<' <> text tagType <> attributeList attribs <>
73+ char '>'
74+ closeTag = text "</" <> text tagType <> char '>'
75+ in if isIndented
76+ then openTag $$ nest 2 contents $$ closeTag
77+ else openTag <> contents <> closeTag
78+
79+-- | Return a self-closing tag of tagType with specified attributes
80+selfClosingTag :: String -> [(String, String)] -> Doc
81+selfClosingTag tagType attribs =
82+ char '<' <> text tagType <> attributeList attribs <> text " />"
83+
84+-- | Put the supplied contents between start and end tags of tagType.
85+inTagsSimple :: String -> Doc -> Doc
86+inTagsSimple tagType = inTags False tagType []
87+
88+-- | Put the supplied contents in indented block btw start and end tags.
89+inTagsIndented :: String -> Doc -> Doc
90+inTagsIndented tagType = inTags True tagType []
91+
92+--
93+-- Docbook writer
94+--
95+
96+-- | Convert list of authors to a docbook <author> section
97+authorToDocbook :: [Char] -> Doc
98+authorToDocbook name = inTagsIndented "author" $
99+ if ',' `elem` name
100+ then -- last name first
101+ let (lastname, rest) = break (==',') name
102+ firstname = removeLeadingSpace rest in
103+ inTagsSimple "firstname" (text $ escapeStringForXML firstname) <>
104+ inTagsSimple "surname" (text $ escapeStringForXML lastname)
105+ else -- last name last
106+ let namewords = words name
107+ lengthname = length namewords
108+ (firstname, lastname) = case lengthname of
109+ 0 -> ("","")
110+ 1 -> ("", name)
111+ n -> (joinWithSep " " (take (n-1) namewords), last namewords)
112+ in inTagsSimple "firstname" (text $ escapeStringForXML firstname) $$
113+ inTagsSimple "surname" (text $ escapeStringForXML lastname)
114+
115+-- | Convert Pandoc document to string in Docbook format.
116+writeDocbook :: WriterOptions -> Pandoc -> String
117+writeDocbook opts (Pandoc (Meta title authors date) blocks) =
118+ let head = if writerStandalone opts
119+ then text (writerHeader opts)
120+ else empty
121+ meta = if writerStandalone opts
122+ then inTagsIndented "articleinfo" $
123+ (inTagsSimple "title" (wrap opts title)) $$
124+ (vcat (map authorToDocbook authors)) $$
125+ (inTagsSimple "date" (text $ escapeStringForXML date))
126+ else empty
127+ elements = hierarchicalize blocks
128+ before = writerIncludeBefore opts
129+ after = writerIncludeAfter opts
130+ body = (if null before then empty else text before) $$
131+ vcat (map (elementToDocbook opts) elements) $$
132+ (if null after then empty else text after)
133+ body' = if writerStandalone opts
134+ then inTagsIndented "article" (meta $$ body)
135+ else body
136+ in render $ head $$ body' $$ text ""
137+
138+-- | Convert an Element to Docbook.
139+elementToDocbook :: WriterOptions -> Element -> Doc
140+elementToDocbook opts (Blk block) = blockToDocbook opts block
141+elementToDocbook opts (Sec title elements) =
142+ -- Docbook doesn't allow sections with no content, so insert some if needed
143+ let elements' = if null elements
144+ then [Blk (Para [])]
145+ else elements
146+ in inTagsIndented "section" $
147+ inTagsSimple "title" (wrap opts title) $$
148+ vcat (map (elementToDocbook opts) elements')
149+
150+-- | Convert a list of Pandoc blocks to Docbook.
151+blocksToDocbook :: WriterOptions -> [Block] -> Doc
152+blocksToDocbook opts = vcat . map (blockToDocbook opts)
153+
154+-- | Auxiliary function to convert Plain block to Para.
155+plainToPara (Plain x) = Para x
156+plainToPara x = x
157+
158+-- | Convert a list of pairs of terms and definitions into a list of
159+-- Docbook varlistentrys.
160+deflistItemsToDocbook :: WriterOptions -> [([Inline],[Block])] -> Doc
161+deflistItemsToDocbook opts items =
162+ vcat $ map (\(term, def) -> deflistItemToDocbook opts term def) items
163+
164+-- | Convert a term and a list of blocks into a Docbook varlistentry.
165+deflistItemToDocbook :: WriterOptions -> [Inline] -> [Block] -> Doc
166+deflistItemToDocbook opts term def =
167+ let def' = map plainToPara def
168+ in inTagsIndented "varlistentry" $
169+ inTagsIndented "term" (inlinesToDocbook opts term) $$
170+ inTagsIndented "listitem" (blocksToDocbook opts def')
171+
172+-- | Convert a list of lists of blocks to a list of Docbook list items.
173+listItemsToDocbook :: WriterOptions -> [[Block]] -> Doc
174+listItemsToDocbook opts items = vcat $ map (listItemToDocbook opts) items
175+
176+-- | Convert a list of blocks into a Docbook list item.
177+listItemToDocbook :: WriterOptions -> [Block] -> Doc
178+listItemToDocbook opts item =
179+ inTagsIndented "listitem" $ blocksToDocbook opts $ map plainToPara item
180+
181+-- | Convert a Pandoc block element to Docbook.
182+blockToDocbook :: WriterOptions -> Block -> Doc
183+blockToDocbook opts Null = empty
184+blockToDocbook opts (Plain lst) = wrap opts lst
185+blockToDocbook opts (Para lst) = inTagsIndented "para" $ wrap opts lst
186+blockToDocbook opts (BlockQuote blocks) =
187+ inTagsIndented "blockquote" $ blocksToDocbook opts blocks
188+blockToDocbook opts (CodeBlock str) =
189+ text "<screen>\n" <> text (escapeStringForXML str) <> text "\n</screen>"
190+blockToDocbook opts (BulletList lst) =
191+ inTagsIndented "itemizedlist" $ listItemsToDocbook opts lst
192+blockToDocbook opts (OrderedList _ []) = empty
193+blockToDocbook opts (OrderedList (start, numstyle, numdelim) (first:rest)) =
194+ let attribs = case numstyle of
195+ DefaultStyle -> []
196+ Decimal -> [("numeration", "arabic")]
197+ UpperAlpha -> [("numeration", "upperalpha")]
198+ LowerAlpha -> [("numeration", "loweralpha")]
199+ UpperRoman -> [("numeration", "upperroman")]
200+ LowerRoman -> [("numeration", "lowerroman")]
201+ items = if start == 1
202+ then listItemsToDocbook opts (first:rest)
203+ else (inTags True "listitem" [("override",show start)]
204+ (blocksToDocbook opts $ map plainToPara first)) $$
205+ listItemsToDocbook opts rest
206+ in inTags True "orderedlist" attribs items
207+blockToDocbook opts (DefinitionList lst) =
208+ inTagsIndented "variablelist" $ deflistItemsToDocbook opts lst
209+blockToDocbook opts (RawHtml str) = text str -- raw XML block
210+blockToDocbook opts HorizontalRule = empty -- not semantic
211+blockToDocbook opts (Table caption aligns widths headers rows) =
212+ let alignStrings = map alignmentToString aligns
213+ captionDoc = if null caption
214+ then empty
215+ else inTagsIndented "caption"
216+ (inlinesToDocbook opts caption)
217+ tableType = if isEmpty captionDoc then "informaltable" else "table"
218+ in inTagsIndented tableType $ captionDoc $$
219+ (colHeadsToDocbook opts alignStrings widths headers) $$
220+ (vcat $ map (tableRowToDocbook opts alignStrings) rows)
221+
222+colHeadsToDocbook opts alignStrings widths headers =
223+ let heads = zipWith3 (\align width item ->
224+ tableItemToDocbook opts "th" align width item)
225+ alignStrings widths headers
226+ in inTagsIndented "tr" $ vcat heads
227+
228+alignmentToString alignment = case alignment of
229+ AlignLeft -> "left"
230+ AlignRight -> "right"
231+ AlignCenter -> "center"
232+ AlignDefault -> "left"
233+
234+tableRowToDocbook opts aligns cols = inTagsIndented "tr" $
235+ vcat $ zipWith3 (tableItemToDocbook opts "td") aligns (repeat 0) cols
236+
237+tableItemToDocbook opts tag align width item =
238+ let attrib = [("align", align)] ++
239+ if width /= 0
240+ then [("style", "{width: " ++
241+ show (truncate (100*width)) ++ "%;}")]
242+ else []
243+ in inTags True tag attrib $ vcat $ map (blockToDocbook opts) item
244+
245+-- | Take list of inline elements and return wrapped doc.
246+wrap :: WriterOptions -> [Inline] -> Doc
247+wrap opts lst = if writerWrapText opts
248+ then fsep $ map (inlinesToDocbook opts) (splitBy Space lst)
249+ else inlinesToDocbook opts lst
250+
251+-- | Convert a list of inline elements to Docbook.
252+inlinesToDocbook :: WriterOptions -> [Inline] -> Doc
253+inlinesToDocbook opts lst = hcat $ map (inlineToDocbook opts) lst
254+
255+-- | Convert an inline element to Docbook.
256+inlineToDocbook :: WriterOptions -> Inline -> Doc
257+inlineToDocbook opts (Str str) = text $ escapeStringForXML str
258+inlineToDocbook opts (Emph lst) =
259+ inTagsSimple "emphasis" $ inlinesToDocbook opts lst
260+inlineToDocbook opts (Strong lst) =
261+ inTags False "emphasis" [("role", "strong")] $ inlinesToDocbook opts lst
262+inlineToDocbook opts (Strikeout lst) =
263+ inTags False "emphasis" [("role", "strikethrough")] $
264+ inlinesToDocbook opts lst
265+inlineToDocbook opts (Superscript lst) =
266+ inTagsSimple "superscript" $ inlinesToDocbook opts lst
267+inlineToDocbook opts (Subscript lst) =
268+ inTagsSimple "subscript" $ inlinesToDocbook opts lst
269+inlineToDocbook opts (Quoted _ lst) =
270+ inTagsSimple "quote" $ inlinesToDocbook opts lst
271+inlineToDocbook opts Apostrophe = char '\''
272+inlineToDocbook opts Ellipses = text "&#8230;"
273+inlineToDocbook opts EmDash = text "&#8212;"
274+inlineToDocbook opts EnDash = text "&#8211;"
275+inlineToDocbook opts (Code str) =
276+ inTagsSimple "literal" $ text (escapeStringForXML str)
277+inlineToDocbook opts (TeX str) = inlineToDocbook opts (Code str)
278+inlineToDocbook opts (HtmlInline str) = empty
279+inlineToDocbook opts LineBreak = text $ "<literallayout></literallayout>"
280+inlineToDocbook opts Space = char ' '
281+inlineToDocbook opts (Link txt (src, tit)) =
282+ if isPrefixOf "mailto:" src
283+ then let src' = drop 7 src
284+ emailLink = inTagsSimple "email" $ text $
285+ escapeStringForXML $ src'
286+ in if txt == [Code src']
287+ then emailLink
288+ else inlinesToDocbook opts txt <+> char '(' <> emailLink <>
289+ char ')'
290+ else inTags False "ulink" [("url", src)] $ inlinesToDocbook opts txt
291+inlineToDocbook opts (Image alt (src, tit)) =
292+ let titleDoc = if null tit
293+ then empty
294+ else inTagsIndented "objectinfo" $
295+ inTagsIndented "title" (text $ escapeStringForXML tit)
296+ in inTagsIndented "inlinemediaobject" $ inTagsIndented "imageobject" $
297+ titleDoc $$ selfClosingTag "imagedata" [("fileref", src)]
298+inlineToDocbook opts (Note contents) =
299+ inTagsIndented "footnote" $ blocksToDocbook opts contents
src/Text/Pandoc/Writers/HTML.hsnew file+458
@@ -0,0 +1,458 @@
1+{-
2+Copyright (C) 2006-7 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.HTML
21+ Copyright : Copyright (C) 2006-7 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 HTML.
29+-}
30+module Text.Pandoc.Writers.HTML ( writeHtml , writeHtmlString ) where
31+import Text.Pandoc.Definition
32+import Text.Pandoc.ASCIIMathML
33+import Text.Pandoc.CharacterReferences ( decodeCharacterReferences )
34+import Text.Pandoc.Shared
35+import Text.Regex ( mkRegex, matchRegex )
36+import Numeric ( showHex )
37+import Data.Char ( ord, toLower )
38+import Data.List ( isPrefixOf, intersperse )
39+import qualified Data.Set as S
40+import Control.Monad.State
41+import Text.XHtml.Transitional
42+
43+data WriterState = WriterState
44+ { stNotes :: [Html] -- ^ List of notes
45+ , stIds :: [String] -- ^ List of header identifiers
46+ , stMath :: Bool -- ^ Math is used in document
47+ , stCSS :: S.Set String -- ^ CSS to include in header
48+ } deriving Show
49+
50+defaultWriterState :: WriterState
51+defaultWriterState = WriterState {stNotes= [], stIds = [],
52+ stMath = False, stCSS = S.empty}
53+
54+-- Helpers to render HTML with the appropriate function.
55+render opts = if writerWrapText opts then renderHtml else showHtml
56+renderFragment opts = if writerWrapText opts
57+ then renderHtmlFragment
58+ else showHtmlFragment
59+
60+-- | Convert Pandoc document to Html string.
61+writeHtmlString :: WriterOptions -> Pandoc -> String
62+writeHtmlString opts =
63+ if writerStandalone opts
64+ then render opts . writeHtml opts
65+ else renderFragment opts . writeHtml opts
66+
67+-- | Convert Pandoc document to Html structure.
68+writeHtml :: WriterOptions -> Pandoc -> Html
69+writeHtml opts (Pandoc (Meta tit authors date) blocks) =
70+ let titlePrefix = writerTitlePrefix opts
71+ topTitle = evalState (inlineListToHtml opts tit) defaultWriterState
72+ topTitle' = if null titlePrefix
73+ then topTitle
74+ else titlePrefix +++ " - " +++ topTitle
75+ metadata = thetitle topTitle' +++
76+ meta ! [httpequiv "Content-Type",
77+ content "text/html; charset=UTF-8"] +++
78+ meta ! [name "generator", content "pandoc"] +++
79+ (toHtmlFromList $
80+ map (\a -> meta ! [name "author", content a]) authors) +++
81+ (if null date
82+ then noHtml
83+ else meta ! [name "date", content date])
84+ titleHeader = if writerStandalone opts && not (null tit) &&
85+ not (writerS5 opts)
86+ then h1 ! [theclass "title"] $ topTitle
87+ else noHtml
88+ headerBlocks = filter isHeaderBlock blocks
89+ ids = uniqueIdentifiers $
90+ map (\(Header _ lst) -> lst) headerBlocks
91+ toc = if writerTableOfContents opts
92+ then tableOfContents opts headerBlocks ids
93+ else noHtml
94+ (blocks', newstate) =
95+ runState (blockListToHtml opts blocks)
96+ (defaultWriterState {stIds = ids})
97+ cssLines = stCSS newstate
98+ css = if S.null cssLines
99+ then noHtml
100+ else style ! [thetype "text/css"] $ primHtml $
101+ '\n':(unlines $ S.toList cssLines)
102+ math = if stMath newstate
103+ then case writerASCIIMathMLURL opts of
104+ Just path -> script ! [src path,
105+ thetype "text/javascript"] $
106+ noHtml
107+ Nothing -> primHtml asciiMathMLScript
108+ else noHtml
109+ head = header $ metadata +++ math +++ css +++
110+ primHtml (writerHeader opts)
111+ notes = reverse (stNotes newstate)
112+ before = primHtml $ writerIncludeBefore opts
113+ after = primHtml $ writerIncludeAfter opts
114+ thebody = before +++ titleHeader +++ toc +++ blocks' +++
115+ footnoteSection opts notes +++ after
116+ in if writerStandalone opts
117+ then head +++ body thebody
118+ else thebody
119+
120+-- | Construct table of contents from list of header blocks and identifiers.
121+-- Assumes there are as many identifiers as header blocks.
122+tableOfContents :: WriterOptions -> [Block] -> [String] -> Html
123+tableOfContents _ [] _ = noHtml
124+tableOfContents opts headers ids =
125+ let opts' = opts { writerIgnoreNotes = True }
126+ contentsTree = hierarchicalize headers
127+ contents = evalState (mapM (elementToListItem opts') contentsTree)
128+ (defaultWriterState {stIds = ids})
129+ in thediv ! [identifier "toc"] $ unordList contents
130+
131+-- | Converts an Element to a list item for a table of contents,
132+-- retrieving the appropriate identifier from state.
133+elementToListItem :: WriterOptions -> Element -> State WriterState Html
134+elementToListItem opts (Blk _) = return noHtml
135+elementToListItem opts (Sec headerText subsecs) = do
136+ st <- get
137+ let ids = stIds st
138+ let (id, rest) = if null ids
139+ then ("", [])
140+ else (head ids, tail ids)
141+ put $ st {stIds = rest}
142+ txt <- inlineListToHtml opts headerText
143+ subHeads <- mapM (elementToListItem opts) subsecs
144+ let subList = if null subHeads
145+ then noHtml
146+ else unordList subHeads
147+ return $ (anchor ! [href ("#" ++ id), identifier ("TOC-" ++ id)] $ txt) +++
148+ subList
149+
150+-- | Convert list of Note blocks to a footnote <div>.
151+-- Assumes notes are sorted.
152+footnoteSection :: WriterOptions -> [Html] -> Html
153+footnoteSection opts notes =
154+ if null notes
155+ then noHtml
156+ else thediv ! [theclass "footnotes"] $ hr +++ (olist << notes)
157+
158+-- | Obfuscate a "mailto:" link using Javascript.
159+obfuscateLink :: WriterOptions -> String -> String -> Html
160+obfuscateLink opts text src =
161+ let emailRegex = mkRegex "^mailto:([^@]*)@(.*)$"
162+ src' = map toLower src
163+ in case (matchRegex emailRegex src') of
164+ (Just [name, domain]) ->
165+ let domain' = substitute "." " dot " domain
166+ at' = obfuscateChar '@'
167+ (linkText, altText) =
168+ if text == drop 7 src' -- autolink
169+ then ("'<code>'+e+'</code>'", name ++ " at " ++ domain')
170+ else ("'" ++ text ++ "'", text ++ " (" ++ name ++ " at " ++
171+ domain' ++ ")")
172+ in if writerStrictMarkdown opts
173+ then -- need to use primHtml or &'s are escaped to &amp; in URL
174+ primHtml $ "<a href=\"" ++ (obfuscateString src')
175+ ++ "\">" ++ (obfuscateString text) ++ "</a>"
176+ else (script ! [thetype "text/javascript"] $
177+ primHtml ("\n<!--\nh='" ++
178+ obfuscateString domain ++ "';a='" ++ at' ++ "';n='" ++
179+ obfuscateString name ++ "';e=n+a+h;\n" ++
180+ "document.write('<a h'+'ref'+'=\"ma'+'ilto'+':'+e+'\">'+" ++
181+ linkText ++ "+'<\\/'+'a'+'>');\n// -->\n")) +++
182+ noscript (primHtml $ obfuscateString altText)
183+ _ -> anchor ! [href src] $ primHtml text -- malformed email
184+
185+-- | Obfuscate character as entity.
186+obfuscateChar :: Char -> String
187+obfuscateChar char =
188+ let num = ord char
189+ numstr = if even num then show num else "x" ++ showHex num ""
190+ in "&#" ++ numstr ++ ";"
191+
192+-- | Obfuscate string using entities.
193+obfuscateString :: String -> String
194+obfuscateString = concatMap obfuscateChar . decodeCharacterReferences
195+
196+-- | True if character is a punctuation character (unicode).
197+isPunctuation :: Char -> Bool
198+isPunctuation c =
199+ let c' = ord c
200+ in if c `elem` "!\"'()*,-./:;<>?[\\]`{|}~" || c' >= 0x2000 && c' <= 0x206F ||
201+ c' >= 0xE000 && c' <= 0xE0FF
202+ then True
203+ else False
204+
205+-- | Add CSS for document header.
206+addToCSS :: String -> State WriterState ()
207+addToCSS item = do
208+ st <- get
209+ let current = stCSS st
210+ put $ st {stCSS = S.insert item current}
211+
212+-- | Convert Pandoc inline list to plain text identifier.
213+inlineListToIdentifier :: [Inline] -> String
214+inlineListToIdentifier [] = ""
215+inlineListToIdentifier (x:xs) =
216+ xAsText ++ inlineListToIdentifier xs
217+ where xAsText = case x of
218+ Str s -> filter (\c -> c == '-' || not (isPunctuation c)) $
219+ concat $ intersperse "-" $ words $ map toLower s
220+ Emph lst -> inlineListToIdentifier lst
221+ Strikeout lst -> inlineListToIdentifier lst
222+ Superscript lst -> inlineListToIdentifier lst
223+ Subscript lst -> inlineListToIdentifier lst
224+ Strong lst -> inlineListToIdentifier lst
225+ Quoted _ lst -> inlineListToIdentifier lst
226+ Code s -> s
227+ Space -> "-"
228+ EmDash -> "-"
229+ EnDash -> "-"
230+ Apostrophe -> ""
231+ Ellipses -> ""
232+ LineBreak -> "-"
233+ TeX _ -> ""
234+ HtmlInline _ -> ""
235+ Link lst _ -> inlineListToIdentifier lst
236+ Image lst _ -> inlineListToIdentifier lst
237+ Note _ -> ""
238+
239+-- | Return unique identifiers for list of inline lists.
240+uniqueIdentifiers :: [[Inline]] -> [String]
241+uniqueIdentifiers ls =
242+ let addIdentifier (nonuniqueIds, uniqueIds) l =
243+ let new = inlineListToIdentifier l
244+ matches = length $ filter (== new) nonuniqueIds
245+ new' = new ++ if matches > 0 then ("-" ++ show matches) else ""
246+ in (new:nonuniqueIds, new':uniqueIds)
247+ in reverse $ snd $ foldl addIdentifier ([],[]) ls
248+
249+-- | Convert Pandoc block element to HTML.
250+blockToHtml :: WriterOptions -> Block -> State WriterState Html
251+blockToHtml opts Null = return $ noHtml
252+blockToHtml opts (Plain lst) = inlineListToHtml opts lst
253+blockToHtml opts (Para lst) = inlineListToHtml opts lst >>= (return . paragraph)
254+blockToHtml opts (RawHtml str) = return $ primHtml str
255+blockToHtml opts (HorizontalRule) = return $ hr
256+blockToHtml opts (CodeBlock str) = return $ pre $ thecode << (str ++ "\n")
257+ -- the final \n for consistency with Markdown.pl
258+blockToHtml opts (BlockQuote blocks) =
259+ -- in S5, treat list in blockquote specially
260+ -- if default is incremental, make it nonincremental;
261+ -- otherwise incremental
262+ if writerS5 opts
263+ then let inc = not (writerIncremental opts) in
264+ case blocks of
265+ [BulletList lst] -> blockToHtml (opts {writerIncremental = inc})
266+ (BulletList lst)
267+ [OrderedList attribs lst] ->
268+ blockToHtml (opts {writerIncremental = inc})
269+ (OrderedList attribs lst)
270+ otherwise -> blockListToHtml opts blocks >>=
271+ (return . blockquote)
272+ else blockListToHtml opts blocks >>= (return . blockquote)
273+blockToHtml opts (Header level lst) = do
274+ contents <- inlineListToHtml opts lst
275+ st <- get
276+ let ids = stIds st
277+ let (id, rest) = if null ids
278+ then ("", [])
279+ else (head ids, tail ids)
280+ put $ st {stIds = rest}
281+ let attribs = if writerStrictMarkdown opts && not (writerTableOfContents opts)
282+ then []
283+ else [identifier id]
284+ let contents' = if writerTableOfContents opts
285+ then anchor ! [href ("#TOC-" ++ id)] $ contents
286+ else contents
287+ return $ case level of
288+ 1 -> h1 contents' ! attribs
289+ 2 -> h2 contents' ! attribs
290+ 3 -> h3 contents' ! attribs
291+ 4 -> h4 contents' ! attribs
292+ 5 -> h5 contents' ! attribs
293+ 6 -> h6 contents' ! attribs
294+ _ -> paragraph contents' ! attribs
295+blockToHtml opts (BulletList lst) = do
296+ contents <- mapM (blockListToHtml opts) lst
297+ let attribs = if writerIncremental opts
298+ then [theclass "incremental"]
299+ else []
300+ return $ unordList ! attribs $ contents
301+blockToHtml opts (OrderedList (startnum, numstyle, _) lst) = do
302+ contents <- mapM (blockListToHtml opts) lst
303+ let numstyle' = camelCaseToHyphenated $ show numstyle
304+ let attribs = (if writerIncremental opts
305+ then [theclass "incremental"]
306+ else []) ++
307+ (if startnum /= 1
308+ then [start startnum]
309+ else []) ++
310+ (if numstyle /= DefaultStyle
311+ then [theclass numstyle']
312+ else [])
313+ if numstyle /= DefaultStyle
314+ then addToCSS $ "ol." ++ numstyle' ++
315+ " { list-style-type: " ++
316+ numstyle' ++ "; }"
317+ else return ()
318+ return $ ordList ! attribs $ contents
319+blockToHtml opts (DefinitionList lst) = do
320+ contents <- mapM (\(term, def) -> do term' <- inlineListToHtml opts term
321+ def' <- blockListToHtml opts def
322+ return $ (term', def')) lst
323+ let attribs = if writerIncremental opts
324+ then [theclass "incremental"]
325+ else []
326+ return $ defList ! attribs $ contents
327+blockToHtml opts (Table capt aligns widths headers rows) = do
328+ let alignStrings = map alignmentToString aligns
329+ captionDoc <- if null capt
330+ then return noHtml
331+ else inlineListToHtml opts capt >>= return . caption
332+ colHeads <- colHeadsToHtml opts alignStrings
333+ widths headers
334+ rows' <- mapM (tableRowToHtml opts alignStrings) rows
335+ return $ table $ captionDoc +++ colHeads +++ rows'
336+
337+colHeadsToHtml opts alignStrings widths headers = do
338+ heads <- sequence $ zipWith3
339+ (\align width item -> tableItemToHtml opts th align width item)
340+ alignStrings widths headers
341+ return $ tr $ toHtmlFromList heads
342+
343+alignmentToString alignment = case alignment of
344+ AlignLeft -> "left"
345+ AlignRight -> "right"
346+ AlignCenter -> "center"
347+ AlignDefault -> "left"
348+
349+tableRowToHtml opts aligns cols =
350+ (sequence $ zipWith3 (tableItemToHtml opts td) aligns (repeat 0) cols) >>=
351+ return . tr . toHtmlFromList
352+
353+tableItemToHtml opts tag align' width item = do
354+ contents <- blockListToHtml opts item
355+ let attrib = [align align'] ++
356+ if width /= 0
357+ then [thestyle ("width: " ++ show (truncate (100*width)) ++
358+ "%;")]
359+ else []
360+ return $ tag ! attrib $ contents
361+
362+blockListToHtml :: WriterOptions -> [Block] -> State WriterState Html
363+blockListToHtml opts lst =
364+ mapM (blockToHtml opts) lst >>= return . toHtmlFromList
365+
366+-- | Convert list of Pandoc inline elements to HTML.
367+inlineListToHtml :: WriterOptions -> [Inline] -> State WriterState Html
368+inlineListToHtml opts lst =
369+ mapM (inlineToHtml opts) lst >>= return . toHtmlFromList
370+
371+-- | Convert Pandoc inline element to HTML.
372+inlineToHtml :: WriterOptions -> Inline -> State WriterState Html
373+inlineToHtml opts inline =
374+ case inline of
375+ (Str str) -> return $ stringToHtml str
376+ (Space) -> return $ stringToHtml " "
377+ (LineBreak) -> return $ br
378+ (EmDash) -> return $ primHtmlChar "mdash"
379+ (EnDash) -> return $ primHtmlChar "ndash"
380+ (Ellipses) -> return $ primHtmlChar "hellip"
381+ (Apostrophe) -> return $ primHtmlChar "rsquo"
382+ (Emph lst) -> inlineListToHtml opts lst >>= return . emphasize
383+ (Strong lst) -> inlineListToHtml opts lst >>= return . strong
384+ (Code str) -> return $ thecode << str
385+ (Strikeout lst) -> addToCSS
386+ ".strikeout { text-decoration: line-through; }" >>
387+ inlineListToHtml opts lst >>=
388+ return . (thespan ! [theclass "strikeout"])
389+ (Superscript lst) -> inlineListToHtml opts lst >>= return . sup
390+ (Subscript lst) -> inlineListToHtml opts lst >>= return . sub
391+ (Quoted quoteType lst) ->
392+ let (leftQuote, rightQuote) = case quoteType of
393+ SingleQuote -> (primHtmlChar "lsquo",
394+ primHtmlChar "rsquo")
395+ DoubleQuote -> (primHtmlChar "ldquo",
396+ primHtmlChar "rdquo")
397+ in do contents <- inlineListToHtml opts lst
398+ return $ leftQuote +++ contents +++ rightQuote
399+ (TeX str) -> (if writerUseASCIIMathML opts
400+ then modify (\st -> st {stMath = True})
401+ else return ()) >> return (stringToHtml str)
402+ (HtmlInline str) -> return $ primHtml str
403+ (Link [Code str] (src,tit)) | "mailto:" `isPrefixOf` src ->
404+ return $ obfuscateLink opts str src
405+ (Link txt (src,tit)) | "mailto:" `isPrefixOf` src -> do
406+ linkText <- inlineListToHtml opts txt
407+ return $ obfuscateLink opts (show linkText) src
408+ (Link txt (src,tit)) -> do
409+ linkText <- inlineListToHtml opts txt
410+ return $ anchor ! ([href src] ++
411+ if null tit then [] else [title tit]) $
412+ linkText
413+ (Image txt (source,tit)) -> do
414+ alternate <- inlineListToHtml opts txt
415+ let alternate' = renderFragment opts alternate
416+ let attributes = [src source] ++
417+ (if null tit
418+ then []
419+ else [title tit]) ++
420+ if null txt
421+ then []
422+ else [alt alternate']
423+ return $ image ! attributes
424+ -- note: null title included, as in Markdown.pl
425+ (Note contents) -> do
426+ st <- get
427+ let notes = stNotes st
428+ let number = (length notes) + 1
429+ let ref = show number
430+ htmlContents <- blockListToNote opts ref contents
431+ -- push contents onto front of notes
432+ put $ st {stNotes = (htmlContents:notes)}
433+ return $ anchor ! [href ("#fn" ++ ref),
434+ theclass "footnoteRef",
435+ identifier ("fnref" ++ ref)] <<
436+ sup << ref
437+
438+blockListToNote :: WriterOptions -> String -> [Block] -> State WriterState Html
439+blockListToNote opts ref blocks =
440+ -- If last block is Para or Plain, include the backlink at the end of
441+ -- that block. Otherwise, insert a new Plain block with the backlink.
442+ let backlink = [HtmlInline $ " <a href=\"#fnref" ++ ref ++
443+ "\" class=\"footnoteBackLink\"" ++
444+ " title=\"Jump back to footnote " ++ ref ++ "\">&#8617;</a>"]
445+ blocks' = if null blocks
446+ then []
447+ else let lastBlock = last blocks
448+ otherBlocks = init blocks
449+ in case lastBlock of
450+ (Para lst) -> otherBlocks ++
451+ [Para (lst ++ backlink)]
452+ (Plain lst) -> otherBlocks ++
453+ [Plain (lst ++ backlink)]
454+ _ -> otherBlocks ++ [lastBlock,
455+ Plain backlink]
456+ in do contents <- blockListToHtml opts blocks'
457+ return $ li ! [identifier ("fn" ++ ref)] $ contents
458+
src/Text/Pandoc/Writers/LaTeX.hsnew file+310
@@ -0,0 +1,310 @@
1+{-
2+Copyright (C) 2006-7 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.LaTeX
21+ Copyright : Copyright (C) 2006-7 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' format into LaTeX.
29+-}
30+module Text.Pandoc.Writers.LaTeX ( writeLaTeX ) where
31+import Text.Pandoc.Definition
32+import Text.Pandoc.Shared
33+import Text.Printf ( printf )
34+import Data.List ( (\\), isInfixOf, isSuffixOf, intersperse )
35+import Data.Char ( toLower )
36+import qualified Data.Set as S
37+import Control.Monad.State
38+import Text.PrettyPrint.HughesPJ hiding ( Str )
39+
40+data WriterState =
41+ WriterState { stIncludes :: S.Set String -- strings to include in header
42+ , stInNote :: Bool -- @True@ if we're in a note
43+ , stOLLevel :: Int } -- level of ordered list nesting
44+
45+-- | Add line to header.
46+addToHeader :: String -> State WriterState ()
47+addToHeader str = do
48+ st <- get
49+ let includes = stIncludes st
50+ put st {stIncludes = S.insert str includes}
51+
52+-- | Convert Pandoc to LaTeX.
53+writeLaTeX :: WriterOptions -> Pandoc -> String
54+writeLaTeX options document =
55+ render $ evalState (pandocToLaTeX options document) $
56+ WriterState { stIncludes = S.empty, stInNote = False, stOLLevel = 1 }
57+
58+pandocToLaTeX :: WriterOptions -> Pandoc -> State WriterState Doc
59+pandocToLaTeX options (Pandoc meta blocks) = do
60+ main <- blockListToLaTeX blocks
61+ head <- if writerStandalone options
62+ then latexHeader options meta
63+ else return empty
64+ let before = if null (writerIncludeBefore options)
65+ then empty
66+ else text (writerIncludeBefore options)
67+ let after = if null (writerIncludeAfter options)
68+ then empty
69+ else text (writerIncludeAfter options)
70+ let body = before $$ main $$ after
71+ let toc = if writerTableOfContents options
72+ then text "\\tableofcontents\n"
73+ else empty
74+ let foot = if writerStandalone options
75+ then text "\\end{document}"
76+ else empty
77+ return $ head $$ toc $$ body $$ foot
78+
79+-- | Insert bibliographic information into LaTeX header.
80+latexHeader :: WriterOptions -- ^ Options, including LaTeX header
81+ -> Meta -- ^ Meta with bibliographic information
82+ -> State WriterState Doc
83+latexHeader options (Meta title authors date) = do
84+ titletext <- if null title
85+ then return empty
86+ else inlineListToLaTeX title >>= return . inCmd "title"
87+ headerIncludes <- get >>= return . S.toList . stIncludes
88+ let extras = text $ unlines headerIncludes
89+ let verbatim = if "\\usepackage{fancyvrb}" `elem` headerIncludes
90+ then text "\\VerbatimFootnotes % allows verbatim text in footnotes"
91+ else empty
92+ let authorstext = text $ "\\author{" ++
93+ joinWithSep "\\\\" (map stringToLaTeX authors) ++ "}"
94+ let datetext = if date == ""
95+ then empty
96+ else text $ "\\date{" ++ stringToLaTeX date ++ "}"
97+ let maketitle = if null title then empty else text "\\maketitle"
98+ let secnumline = if (writerNumberSections options)
99+ then empty
100+ else text "\\setcounter{secnumdepth}{0}"
101+ let baseHeader = text $ writerHeader options
102+ let header = baseHeader $$ extras
103+ return $ header $$ secnumline $$ verbatim $$ titletext $$ authorstext $$
104+ datetext $$ text "\\begin{document}" $$ maketitle $$ text ""
105+
106+-- escape things as needed for LaTeX
107+
108+stringToLaTeX :: String -> String
109+stringToLaTeX = escapeStringUsing latexEscapes
110+ where latexEscapes = backslashEscapes "{}$%&_#" ++
111+ [ ('^', "\\^{}")
112+ , ('\\', "\\textbackslash{}")
113+ , ('~', "\\ensuremath{\\sim}")
114+ , ('|', "\\textbar{}")
115+ , ('<', "\\textless{}")
116+ , ('>', "\\textgreater{}")
117+ ]
118+
119+-- | Puts contents into LaTeX command.
120+inCmd :: String -> Doc -> Doc
121+inCmd cmd contents = char '\\' <> text cmd <> braces contents
122+
123+-- | Remove all code elements from list of inline elements
124+-- (because it's illegal to have verbatim inside some command arguments)
125+deVerb :: [Inline] -> [Inline]
126+deVerb [] = []
127+deVerb ((Code str):rest) =
128+ (TeX $ "\\texttt{" ++ stringToLaTeX str ++ "}"):(deVerb rest)
129+deVerb (other:rest) = other:(deVerb rest)
130+
131+-- | Convert Pandoc block element to LaTeX.
132+blockToLaTeX :: Block -- ^ Block to convert
133+ -> State WriterState Doc
134+blockToLaTeX Null = return empty
135+blockToLaTeX (Plain lst) = wrapped inlineListToLaTeX lst >>= return
136+blockToLaTeX (Para lst) =
137+ wrapped inlineListToLaTeX lst >>= return . (<> char '\n')
138+blockToLaTeX (BlockQuote lst) = do
139+ contents <- blockListToLaTeX lst
140+ return $ text "\\begin{quote}" $$ contents $$ text "\\end{quote}"
141+blockToLaTeX (CodeBlock str) = do
142+ st <- get
143+ env <- if stInNote st
144+ then do addToHeader "\\usepackage{fancyvrb}"
145+ return "Verbatim"
146+ else return "verbatim"
147+ return $ text ("\\begin{" ++ env ++ "}\n") <> text str <>
148+ text ("\n\\end{" ++ env ++ "}")
149+blockToLaTeX (RawHtml str) = return empty
150+blockToLaTeX (BulletList lst) = do
151+ items <- mapM listItemToLaTeX lst
152+ return $ text "\\begin{itemize}" $$ vcat items $$ text "\\end{itemize}"
153+blockToLaTeX (OrderedList (start, numstyle, numdelim) lst) = do
154+ st <- get
155+ let oldlevel = stOLLevel st
156+ put $ st {stOLLevel = oldlevel + 1}
157+ items <- mapM listItemToLaTeX lst
158+ modify (\st -> st {stOLLevel = oldlevel})
159+ exemplar <- if numstyle /= DefaultStyle || numdelim /= DefaultDelim
160+ then do addToHeader "\\usepackage{enumerate}"
161+ return $ char '[' <>
162+ text (head (orderedListMarkers (1, numstyle,
163+ numdelim))) <> char ']'
164+ else return empty
165+ let resetcounter = if start /= 1 && oldlevel <= 4
166+ then text $ "\\setcounter{enum" ++
167+ map toLower (toRomanNumeral oldlevel) ++
168+ "}{" ++ show (start - 1) ++ "}"
169+ else empty
170+ return $ text "\\begin{enumerate}" <> exemplar $$ resetcounter $$
171+ vcat items $$ text "\\end{enumerate}"
172+blockToLaTeX (DefinitionList lst) = do
173+ items <- mapM defListItemToLaTeX lst
174+ return $ text "\\begin{description}" $$ vcat items $$
175+ text "\\end{description}"
176+blockToLaTeX HorizontalRule = return $ text $
177+ "\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n"
178+blockToLaTeX (Header level lst) = do
179+ txt <- inlineListToLaTeX (deVerb lst)
180+ return $ if (level > 0) && (level <= 3)
181+ then text ("\\" ++ (concat (replicate (level - 1) "sub")) ++
182+ "section{") <> txt <> text "}\n"
183+ else txt <> char '\n'
184+blockToLaTeX (Table caption aligns widths heads rows) = do
185+ headers <- tableRowToLaTeX heads
186+ captionText <- inlineListToLaTeX caption
187+ rows' <- mapM tableRowToLaTeX rows
188+ let colWidths = map (printf "%.2f") widths
189+ let colDescriptors = concat $ zipWith
190+ (\width align -> ">{\\PBS" ++
191+ (case align of
192+ AlignLeft -> "\\raggedright"
193+ AlignRight -> "\\raggedleft"
194+ AlignCenter -> "\\centering"
195+ AlignDefault -> "\\raggedright") ++
196+ "\\hspace{0pt}}p{" ++ width ++
197+ "\\columnwidth}")
198+ colWidths aligns
199+ let tableBody = text ("\\begin{tabular}{" ++ colDescriptors ++ "}") $$
200+ headers $$ text "\\hline" $$ vcat rows' $$
201+ text "\\end{tabular}"
202+ let centered txt = text "\\begin{center}" $$ txt $$ text "\\end{center}"
203+ addToHeader "\\usepackage{array}\n\
204+ \% This is needed because raggedright in table elements redefines \\\\:\n\
205+ \\\newcommand{\\PreserveBackslash}[1]{\\let\\temp=\\\\#1\\let\\\\=\\temp}\n\
206+ \\\let\\PBS=\\PreserveBackslash"
207+ return $ if isEmpty captionText
208+ then centered tableBody <> char '\n'
209+ else text "\\begin{table}[h]" $$ centered tableBody $$
210+ inCmd "caption" captionText $$ text "\\end{table}\n"
211+
212+blockListToLaTeX lst = mapM blockToLaTeX lst >>= return . vcat
213+
214+tableRowToLaTeX cols = mapM blockListToLaTeX cols >>=
215+ return . ($$ text "\\\\") . foldl (\row item -> row $$
216+ (if isEmpty row then empty else text " & ") <> item) empty
217+
218+listItemToLaTeX lst = blockListToLaTeX lst >>= return . (text "\\item " $$) .
219+ (nest 2)
220+
221+defListItemToLaTeX (term, def) = do
222+ term' <- inlineListToLaTeX $ deVerb term
223+ def' <- blockListToLaTeX def
224+ return $ text "\\item[" <> term' <> text "]" $$ def'
225+
226+-- | Convert list of inline elements to LaTeX.
227+inlineListToLaTeX :: [Inline] -- ^ Inlines to convert
228+ -> State WriterState Doc
229+inlineListToLaTeX lst = mapM inlineToLaTeX lst >>= return . hcat
230+
231+isQuoted :: Inline -> Bool
232+isQuoted (Quoted _ _) = True
233+isQuoted Apostrophe = True
234+isQuoted _ = False
235+
236+-- | Convert inline element to LaTeX
237+inlineToLaTeX :: Inline -- ^ Inline to convert
238+ -> State WriterState Doc
239+inlineToLaTeX (Emph lst) =
240+ inlineListToLaTeX (deVerb lst) >>= return . inCmd "emph"
241+inlineToLaTeX (Strong lst) =
242+ inlineListToLaTeX (deVerb lst) >>= return . inCmd "textbf"
243+inlineToLaTeX (Strikeout lst) = do
244+ contents <- inlineListToLaTeX $ deVerb lst
245+ addToHeader "\\usepackage[normalem]{ulem}"
246+ return $ inCmd "sout" contents
247+inlineToLaTeX (Superscript lst) =
248+ inlineListToLaTeX (deVerb lst) >>= return . inCmd "textsuperscript"
249+inlineToLaTeX (Subscript lst) = do
250+ contents <- inlineListToLaTeX $ deVerb lst
251+ -- oddly, latex includes \textsuperscript but not \textsubscript
252+ -- so we have to define it:
253+ addToHeader "\\newcommand{\\textsubscript}[1]{\\ensuremath{_{\\scriptsize\\textrm{#1}}}}"
254+ return $ inCmd "textsubscript" contents
255+inlineToLaTeX (Code str) = do
256+ st <- get
257+ if stInNote st
258+ then do addToHeader "\\usepackage{fancyvrb}"
259+ else return ()
260+ let chr = ((enumFromTo '!' '~') \\ str) !! 0
261+ return $ text $ "\\verb" ++ [chr] ++ str ++ [chr]
262+inlineToLaTeX (Quoted SingleQuote lst) = do
263+ contents <- inlineListToLaTeX lst
264+ let s1 = if (not (null lst)) && (isQuoted (head lst))
265+ then text "\\,"
266+ else empty
267+ let s2 = if (not (null lst)) && (isQuoted (last lst))
268+ then text "\\,"
269+ else empty
270+ return $ char '`' <> s1 <> contents <> s2 <> char '\''
271+inlineToLaTeX (Quoted DoubleQuote lst) = do
272+ contents <- inlineListToLaTeX lst
273+ let s1 = if (not (null lst)) && (isQuoted (head lst))
274+ then text "\\,"
275+ else empty
276+ let s2 = if (not (null lst)) && (isQuoted (last lst))
277+ then text "\\,"
278+ else empty
279+ return $ text "``" <> s1 <> contents <> s2 <> text "''"
280+inlineToLaTeX Apostrophe = return $ char '\''
281+inlineToLaTeX EmDash = return $ text "---"
282+inlineToLaTeX EnDash = return $ text "--"
283+inlineToLaTeX Ellipses = return $ text "\\ldots{}"
284+inlineToLaTeX (Str str) = return $ text $ stringToLaTeX str
285+inlineToLaTeX (TeX str) = return $ text str
286+inlineToLaTeX (HtmlInline str) = return empty
287+inlineToLaTeX (LineBreak) = return $ text "\\\\"
288+inlineToLaTeX Space = return $ char ' '
289+inlineToLaTeX (Link txt (src, _)) = do
290+ addToHeader "\\usepackage[breaklinks=true]{hyperref}"
291+ case txt of
292+ [Code x] | x == src -> -- autolink
293+ do addToHeader "\\usepackage{url}"
294+ return $ text $ "\\url{" ++ x ++ "}"
295+ _ -> do contents <- inlineListToLaTeX $ deVerb txt
296+ return $ text ("\\href{" ++ src ++ "}{") <> contents <>
297+ char '}'
298+inlineToLaTeX (Image alternate (source, tit)) = do
299+ addToHeader "\\usepackage{graphicx}"
300+ return $ text $ "\\includegraphics{" ++ source ++ "}"
301+inlineToLaTeX (Note contents) = do
302+ st <- get
303+ put (st {stInNote = True})
304+ contents' <- blockListToLaTeX contents
305+ modify (\st -> st {stInNote = False})
306+ let rawnote = stripTrailingNewlines $ render contents'
307+ -- note: a \n before } is needed when note ends with a Verbatim environment
308+ let optNewline = "\\end{Verbatim}" `isSuffixOf` rawnote
309+ return $ text "%\n\\footnote{" <>
310+ text rawnote <> (if optNewline then char '\n' else empty) <> char '}'
src/Text/Pandoc/Writers/Man.hsnew file+293
@@ -0,0 +1,293 @@
1+{-
2+Copyright (C) 2007 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.Man
21+ Copyright : Copyright (C) 2007 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+-}
31+module Text.Pandoc.Writers.Man ( writeMan) where
32+import Text.Pandoc.Definition
33+import Text.Pandoc.Shared
34+import Text.Printf ( printf )
35+import Data.List ( isPrefixOf, drop, nub, intersperse )
36+import Text.PrettyPrint.HughesPJ hiding ( Str )
37+import Control.Monad.State
38+
39+type Notes = [[Block]]
40+type Preprocessors = [String] -- e.g. "t" for tbl
41+type WriterState = (Notes, Preprocessors)
42+
43+-- | Convert Pandoc to Man.
44+writeMan :: WriterOptions -> Pandoc -> String
45+writeMan opts document = render $ evalState (pandocToMan opts document) ([],[])
46+
47+-- | Return groff man representation of document.
48+pandocToMan :: WriterOptions -> Pandoc -> State WriterState Doc
49+pandocToMan opts (Pandoc meta blocks) = do
50+ let before = writerIncludeBefore opts
51+ let after = writerIncludeAfter opts
52+ let before' = if null before then empty else text before
53+ let after' = if null after then empty else text after
54+ (head, foot) <- metaToMan opts meta
55+ body <- blockListToMan opts blocks
56+ (notes, preprocessors) <- get
57+ let preamble = if null preprocessors || not (writerStandalone opts)
58+ then empty
59+ else text $ ".\\\" " ++ concat (nub preprocessors)
60+ notes' <- notesToMan opts (reverse notes)
61+ return $ preamble $$ head $$ before' $$ body $$ notes' $$ foot $$ after'
62+
63+-- | Insert bibliographic information into Man header and footer.
64+metaToMan :: WriterOptions -- ^ Options, including Man header
65+ -> Meta -- ^ Meta with bibliographic information
66+ -> State WriterState (Doc, Doc)
67+metaToMan options (Meta title authors date) = do
68+ titleText <- inlineListToMan options title
69+ let (cmdName, rest) = break (== ' ') $ render titleText
70+ let (title', section) = case reverse cmdName of
71+ (')':d:'(':xs) | d `elem` ['0'..'9'] ->
72+ (text (reverse xs), char d)
73+ xs -> (text (reverse xs), doubleQuotes empty)
74+ let extras = map (doubleQuotes . text . removeLeadingTrailingSpace) $
75+ splitBy '|' rest
76+ let head = (text ".TH") <+> title' <+> section <+>
77+ doubleQuotes (text date) <+> hsep extras
78+ let foot = case length authors of
79+ 0 -> empty
80+ 1 -> text ".SH AUTHOR" $$ (text $ joinWithSep ", " authors)
81+ 2 -> text ".SH AUTHORS" $$ (text $ joinWithSep ", " authors)
82+ return $ if writerStandalone options
83+ then (head, foot)
84+ else (empty, empty)
85+
86+-- | Return man representation of notes.
87+notesToMan :: WriterOptions -> [[Block]] -> State WriterState Doc
88+notesToMan opts notes =
89+ if null notes
90+ then return empty
91+ else mapM (\(num, note) -> noteToMan opts num note) (zip [1..] notes) >>=
92+ return . (text ".SH NOTES" $$) . vcat
93+
94+-- | Return man representation of a note.
95+noteToMan :: WriterOptions -> Int -> [Block] -> State WriterState Doc
96+noteToMan opts num note = do
97+ contents <- blockListToMan opts note
98+ let marker = text "\n.SS [" <> text (show num) <> char ']'
99+ return $ marker $$ contents
100+
101+-- | Association list of characters to escape.
102+manEscapes :: [(Char, String)]
103+manEscapes = [('\160', "\\ "), ('\'', "\\[aq]")] ++ backslashEscapes "\".@\\"
104+
105+-- | Escape special characters for Man.
106+escapeString :: String -> String
107+escapeString = escapeStringUsing manEscapes
108+
109+-- | Escape a literal (code) section for Man.
110+escapeCode :: String -> String
111+escapeCode = escapeStringUsing (manEscapes ++ backslashEscapes "\t ")
112+
113+-- | Convert Pandoc block element to man.
114+blockToMan :: WriterOptions -- ^ Options
115+ -> Block -- ^ Block element
116+ -> State WriterState Doc
117+blockToMan opts Null = return empty
118+blockToMan opts (Plain inlines) =
119+ wrapIfNeeded opts (inlineListToMan opts) inlines
120+blockToMan opts (Para inlines) = do
121+ contents <- wrapIfNeeded opts (inlineListToMan opts) inlines
122+ return $ text ".PP" $$ contents
123+blockToMan opts (RawHtml str) = return $ text str
124+blockToMan opts HorizontalRule = return $ text $ ".PP\n * * * * *"
125+blockToMan opts (Header level inlines) = do
126+ contents <- inlineListToMan opts inlines
127+ let heading = case level of
128+ 1 -> ".SH "
129+ _ -> ".SS "
130+ return $ text heading <> contents
131+blockToMan opts (CodeBlock str) = return $
132+ text ".PP" $$ text "\\f[CR]" $$
133+ text ((unlines . map (" " ++) . lines) (escapeCode str)) <> text "\\f[]"
134+blockToMan opts (BlockQuote blocks) = do
135+ contents <- blockListToMan opts blocks
136+ return $ text ".RS" $$ contents $$ text ".RE"
137+blockToMan opts (Table caption alignments widths headers rows) =
138+ let aligncode AlignLeft = "l"
139+ aligncode AlignRight = "r"
140+ aligncode AlignCenter = "c"
141+ aligncode AlignDefault = "l"
142+ in do
143+ caption' <- inlineListToMan opts caption
144+ modify (\(notes, preprocessors) -> (notes, "t":preprocessors))
145+ let iwidths = map (printf "w(%0.2fn)" . (70 *)) widths
146+ -- 78n default width - 8n indent = 70n
147+ let coldescriptions = text $ joinWithSep " "
148+ (zipWith (\align width -> aligncode align ++ width)
149+ alignments iwidths) ++ "."
150+ colheadings <- mapM (blockListToMan opts) headers
151+ let makeRow cols = text "T{" $$
152+ (vcat $ intersperse (text "T}@T{") cols) $$
153+ text "T}"
154+ let colheadings' = makeRow colheadings
155+ body <- mapM (\row -> do
156+ cols <- mapM (blockListToMan opts) row
157+ return $ makeRow cols) rows
158+ return $ text ".PP" $$ caption' $$
159+ text ".TS" $$ text "tab(@);" $$ coldescriptions $$
160+ colheadings' $$ char '_' $$ vcat body $$ text ".TE"
161+
162+blockToMan opts (BulletList items) = do
163+ contents <- mapM (bulletListItemToMan opts) items
164+ return (vcat contents)
165+blockToMan opts (OrderedList attribs items) = do
166+ let markers = take (length items) $ orderedListMarkers attribs
167+ let indent = 1 + (maximum $ map length markers)
168+ contents <- mapM (\(num, item) -> orderedListItemToMan opts num indent item) $
169+ zip markers items
170+ return (vcat contents)
171+blockToMan opts (DefinitionList items) = do
172+ contents <- mapM (definitionListItemToMan opts) items
173+ return (vcat contents)
174+
175+-- | Convert bullet list item (list of blocks) to man.
176+bulletListItemToMan :: WriterOptions -> [Block] -> State WriterState Doc
177+bulletListItemToMan opts [] = return empty
178+bulletListItemToMan opts ((Para first):rest) =
179+ bulletListItemToMan opts ((Plain first):rest)
180+bulletListItemToMan opts ((Plain first):rest) = do
181+ first' <- blockToMan opts (Plain first)
182+ rest' <- blockListToMan opts rest
183+ let first'' = text ".IP \\[bu] 2" $$ first'
184+ let rest'' = if null rest
185+ then empty
186+ else text ".RS 2" $$ rest' $$ text ".RE"
187+ return (first'' $$ rest'')
188+bulletListItemToMan opts (first:rest) = do
189+ first' <- blockToMan opts first
190+ rest' <- blockListToMan opts rest
191+ return $ text "\\[bu] .RS 2" $$ first' $$ rest' $$ text ".RE"
192+
193+-- | Convert ordered list item (a list of blocks) to man.
194+orderedListItemToMan :: WriterOptions -- ^ options
195+ -> String -- ^ order marker for list item
196+ -> Int -- ^ number of spaces to indent
197+ -> [Block] -- ^ list item (list of blocks)
198+ -> State WriterState Doc
199+orderedListItemToMan _ _ _ [] = return empty
200+orderedListItemToMan opts num indent ((Para first):rest) =
201+ orderedListItemToMan opts num indent ((Plain first):rest)
202+orderedListItemToMan opts num indent (first:rest) = do
203+ first' <- blockToMan opts first
204+ rest' <- blockListToMan opts rest
205+ let num' = printf ("%" ++ show (indent - 1) ++ "s") num
206+ let first'' = text (".IP \"" ++ num' ++ "\" " ++ show indent) $$ first'
207+ let rest'' = if null rest
208+ then empty
209+ else text ".RS 4" $$ rest' $$ text ".RE"
210+ return $ first'' $$ rest''
211+
212+-- | Convert definition list item (label, list of blocks) to man.
213+definitionListItemToMan :: WriterOptions
214+ -> ([Inline],[Block])
215+ -> State WriterState Doc
216+definitionListItemToMan opts (label, items) = do
217+ labelText <- inlineListToMan opts label
218+ contents <- if null items
219+ then return empty
220+ else do
221+ let (first, rest) = case items of
222+ ((Para x):y) -> (Plain x,y)
223+ (x:y) -> (x,y)
224+ rest' <- mapM (\item -> blockToMan opts item)
225+ rest >>= (return . vcat)
226+ first' <- blockToMan opts first
227+ return $ first' $$ text ".RS" $$ rest' $$ text ".RE"
228+ return $ text ".TP\n.B " <> labelText $+$ contents
229+
230+-- | Convert list of Pandoc block elements to man.
231+blockListToMan :: WriterOptions -- ^ Options
232+ -> [Block] -- ^ List of block elements
233+ -> State WriterState Doc
234+blockListToMan opts blocks =
235+ mapM (blockToMan opts) blocks >>= (return . vcat)
236+
237+-- | Convert list of Pandoc inline elements to man.
238+inlineListToMan :: WriterOptions -> [Inline] -> State WriterState Doc
239+inlineListToMan opts lst = mapM (inlineToMan opts) lst >>= (return . hcat)
240+
241+-- | Convert Pandoc inline element to man.
242+inlineToMan :: WriterOptions -> Inline -> State WriterState Doc
243+inlineToMan opts (Emph lst) = do
244+ contents <- inlineListToMan opts lst
245+ return $ text "\\f[I]" <> contents <> text "\\f[]"
246+inlineToMan opts (Strong lst) = do
247+ contents <- inlineListToMan opts lst
248+ return $ text "\\f[B]" <> contents <> text "\\f[]"
249+inlineToMan opts (Strikeout lst) = do
250+ contents <- inlineListToMan opts lst
251+ return $ text "[STRIKEOUT:" <> contents <> char ']'
252+inlineToMan opts (Superscript lst) = do
253+ contents <- inlineListToMan opts lst
254+ return $ char '^' <> contents <> char '^'
255+inlineToMan opts (Subscript lst) = do
256+ contents <- inlineListToMan opts lst
257+ return $ char '~' <> contents <> char '~'
258+inlineToMan opts (Quoted SingleQuote lst) = do
259+ contents <- inlineListToMan opts lst
260+ return $ char '`' <> contents <> char '\''
261+inlineToMan opts (Quoted DoubleQuote lst) = do
262+ contents <- inlineListToMan opts lst
263+ return $ text "\\[lq]" <> contents <> text "\\[rq]"
264+inlineToMan opts EmDash = return $ text "\\[em]"
265+inlineToMan opts EnDash = return $ text "\\[en]"
266+inlineToMan opts Apostrophe = return $ char '\''
267+inlineToMan opts Ellipses = return $ text "\\&..."
268+inlineToMan opts (Code str) =
269+ return $ text $ "\\f[B]" ++ escapeCode str ++ "\\f[]"
270+inlineToMan opts (Str str) = return $ text $ escapeString str
271+inlineToMan opts (TeX str) = return $ text $ escapeCode str
272+inlineToMan opts (HtmlInline str) = return $ text $ escapeCode str
273+inlineToMan opts (LineBreak) = return $ text "\n.PD 0\n.P\n.PD\n"
274+inlineToMan opts Space = return $ char ' '
275+inlineToMan opts (Link txt (src, _)) = do
276+ linktext <- inlineListToMan opts txt
277+ let srcSuffix = if isPrefixOf "mailto:" src then drop 7 src else src
278+ return $ if txt == [Code srcSuffix]
279+ then char '<' <> text srcSuffix <> char '>'
280+ else linktext <> text " (" <> text src <> char ')'
281+inlineToMan opts (Image alternate (source, tit)) = do
282+ let txt = if (null alternate) || (alternate == [Str ""]) ||
283+ (alternate == [Str source]) -- to prevent autolinks
284+ then [Str "image"]
285+ else alternate
286+ linkPart <- inlineToMan opts (Link txt (source, tit))
287+ return $ char '[' <> text "IMAGE: " <> linkPart <> char ']'
288+inlineToMan opts (Note contents) = do
289+ modify (\(notes, prep) -> (contents:notes, prep)) -- add to notes in state
290+ (notes, _) <- get
291+ let ref = show $ (length notes)
292+ return $ char '[' <> text ref <> char ']'
293+
src/Text/Pandoc/Writers/Markdown.hsnew file+373
@@ -0,0 +1,373 @@
1+{-
2+Copyright (C) 2006-7 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.Markdown
21+ Copyright : Copyright (C) 2006-7 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 markdown-formatted plain text.
29+
30+Markdown: <http://daringfireball.net/projects/markdown/>
31+-}
32+module Text.Pandoc.Writers.Markdown ( writeMarkdown) where
33+import Text.Pandoc.Definition
34+import Text.Pandoc.Shared
35+import Text.Pandoc.Blocks
36+import Text.ParserCombinators.Parsec ( parse, (<|>), GenParser )
37+import Data.List ( group, isPrefixOf, drop, find, intersperse )
38+import Text.PrettyPrint.HughesPJ hiding ( Str )
39+import Control.Monad.State
40+
41+type Notes = [[Block]]
42+type Refs = KeyTable
43+type WriterState = (Notes, Refs)
44+
45+-- | Convert Pandoc to Markdown.
46+writeMarkdown :: WriterOptions -> Pandoc -> String
47+writeMarkdown opts document =
48+ render $ evalState (pandocToMarkdown opts document) ([],[])
49+
50+-- | Return markdown representation of document.
51+pandocToMarkdown :: WriterOptions -> Pandoc -> State WriterState Doc
52+pandocToMarkdown opts (Pandoc meta blocks) = do
53+ let before = writerIncludeBefore opts
54+ let after = writerIncludeAfter opts
55+ let before' = if null before then empty else text before
56+ let after' = if null after then empty else text after
57+ metaBlock <- metaToMarkdown opts meta
58+ let head = if writerStandalone opts
59+ then metaBlock $+$ text (writerHeader opts)
60+ else empty
61+ let headerBlocks = filter isHeaderBlock blocks
62+ let toc = if writerTableOfContents opts
63+ then tableOfContents opts headerBlocks
64+ else empty
65+ body <- blockListToMarkdown opts blocks
66+ (notes, _) <- get
67+ notes' <- notesToMarkdown opts (reverse notes)
68+ (_, refs) <- get -- note that the notes may contain refs
69+ refs' <- keyTableToMarkdown opts (reverse refs)
70+ return $ head $+$ before' $+$ toc $+$ body $+$ text "" $+$
71+ notes' $+$ text "" $+$ refs' $+$ after'
72+
73+-- | Return markdown representation of reference key table.
74+keyTableToMarkdown :: WriterOptions -> KeyTable -> State WriterState Doc
75+keyTableToMarkdown opts refs = mapM (keyToMarkdown opts) refs >>= return . vcat
76+
77+-- | Return markdown representation of a reference key.
78+keyToMarkdown :: WriterOptions
79+ -> ([Inline], (String, String))
80+ -> State WriterState Doc
81+keyToMarkdown opts (label, (src, tit)) = do
82+ label' <- inlineListToMarkdown opts label
83+ let tit' = if null tit then empty else text $ " \"" ++ tit ++ "\""
84+ return $ text " " <> char '[' <> label' <> char ']' <> text ": " <>
85+ text src <> tit'
86+
87+-- | Return markdown representation of notes.
88+notesToMarkdown :: WriterOptions -> [[Block]] -> State WriterState Doc
89+notesToMarkdown opts notes =
90+ mapM (\(num, note) -> noteToMarkdown opts num note) (zip [1..] notes) >>=
91+ return . vcat
92+
93+-- | Return markdown representation of a note.
94+noteToMarkdown :: WriterOptions -> Int -> [Block] -> State WriterState Doc
95+noteToMarkdown opts num blocks = do
96+ contents <- blockListToMarkdown opts blocks
97+ let marker = text "[^" <> text (show num) <> text "]:"
98+ return $ hang marker (writerTabStop opts) contents
99+
100+-- | Escape special characters for Markdown.
101+escapeString :: String -> String
102+escapeString = escapeStringUsing markdownEscapes
103+ where markdownEscapes = ('\160', "&nbsp;"):(backslashEscapes "`<\\*_^~")
104+
105+-- | Convert bibliographic information into Markdown header.
106+metaToMarkdown :: WriterOptions -> Meta -> State WriterState Doc
107+metaToMarkdown opts (Meta title authors date) = do
108+ title' <- titleToMarkdown opts title
109+ authors' <- authorsToMarkdown authors
110+ date' <- dateToMarkdown date
111+ return $ title' $+$ authors' $+$ date'
112+
113+titleToMarkdown :: WriterOptions -> [Inline] -> State WriterState Doc
114+titleToMarkdown opts [] = return empty
115+titleToMarkdown opts lst = do
116+ contents <- inlineListToMarkdown opts lst
117+ return $ text "% " <> contents
118+
119+authorsToMarkdown :: [String] -> State WriterState Doc
120+authorsToMarkdown [] = return empty
121+authorsToMarkdown lst = return $
122+ text "% " <> text (joinWithSep ", " (map escapeString lst))
123+
124+dateToMarkdown :: String -> State WriterState Doc
125+dateToMarkdown [] = return empty
126+dateToMarkdown str = return $ text "% " <> text (escapeString str)
127+
128+-- | Construct table of contents from list of header blocks.
129+tableOfContents :: WriterOptions -> [Block] -> Doc
130+tableOfContents opts headers =
131+ let opts' = opts { writerIgnoreNotes = True }
132+ contents = BulletList $ map elementToListItem $ hierarchicalize headers
133+ in evalState (blockToMarkdown opts' contents) ([],[])
134+
135+-- | Converts an Element to a list item for a table of contents,
136+elementToListItem :: Element -> [Block]
137+elementToListItem (Blk _) = []
138+elementToListItem (Sec headerText subsecs) = [Plain headerText] ++
139+ if null subsecs
140+ then []
141+ else [BulletList $ map elementToListItem subsecs]
142+
143+-- | Ordered list start parser for use in Para below.
144+olMarker :: GenParser Char st Char
145+olMarker = do (start, style, delim) <- anyOrderedListMarker
146+ if delim == Period &&
147+ (style == UpperAlpha || (style == UpperRoman &&
148+ start `elem` [1, 5, 10, 50, 100, 500, 1000]))
149+ then spaceChar >> spaceChar
150+ else spaceChar
151+
152+-- | True if string begins with an ordered list marker
153+beginsWithOrderedListMarker :: String -> Bool
154+beginsWithOrderedListMarker str =
155+ case parse olMarker "para start" str of
156+ Left _ -> False
157+ Right _ -> True
158+
159+wrappedMarkdown :: WriterOptions -> [Inline] -> State WriterState Doc
160+wrappedMarkdown opts inlines = do
161+ let chunks = splitBy LineBreak inlines
162+ let chunks' = if null chunks
163+ then []
164+ else (map (++ [Str " "]) $ init chunks) ++ [last chunks]
165+ lns <- mapM (wrapIfNeeded opts (inlineListToMarkdown opts)) chunks'
166+ return $ vcat lns
167+
168+-- | Convert Pandoc block element to markdown.
169+blockToMarkdown :: WriterOptions -- ^ Options
170+ -> Block -- ^ Block element
171+ -> State WriterState Doc
172+blockToMarkdown opts Null = return empty
173+blockToMarkdown opts (Plain inlines) =
174+ wrappedMarkdown opts inlines
175+blockToMarkdown opts (Para inlines) = do
176+ contents <- wrappedMarkdown opts inlines
177+ -- escape if para starts with ordered list marker
178+ let esc = if (not (writerStrictMarkdown opts)) &&
179+ beginsWithOrderedListMarker (render contents)
180+ then char '\\'
181+ else empty
182+ return $ esc <> contents <> text "\n"
183+blockToMarkdown opts (RawHtml str) = return $ text str
184+blockToMarkdown opts HorizontalRule = return $ text "\n* * * * *\n"
185+blockToMarkdown opts (Header level inlines) = do
186+ contents <- inlineListToMarkdown opts inlines
187+ return $ text ((replicate level '#') ++ " ") <> contents <> text "\n"
188+blockToMarkdown opts (CodeBlock str) = return $
189+ (nest (writerTabStop opts) $ vcat $ map text (lines str)) <> text "\n"
190+blockToMarkdown opts (BlockQuote blocks) = do
191+ contents <- blockListToMarkdown opts blocks
192+ return $ (vcat $ map (text . ("> " ++)) $ lines $ render contents) <>
193+ text "\n"
194+blockToMarkdown opts (Table caption aligns widths headers rows) = do
195+ caption' <- inlineListToMarkdown opts caption
196+ let caption'' = if null caption
197+ then empty
198+ else text "" $+$ (text "Table: " <> caption')
199+ headers' <- mapM (blockListToMarkdown opts) headers
200+ let widthsInChars = map (floor . (78 *)) widths
201+ let alignHeader alignment = case alignment of
202+ AlignLeft -> leftAlignBlock
203+ AlignCenter -> centerAlignBlock
204+ AlignRight -> rightAlignBlock
205+ AlignDefault -> leftAlignBlock
206+ let makeRow = hsepBlocks . (zipWith alignHeader aligns) .
207+ (zipWith docToBlock widthsInChars)
208+ let head = makeRow headers'
209+ rows' <- mapM (\row -> do cols <- mapM (blockListToMarkdown opts) row
210+ return $ makeRow cols) rows
211+ let tableWidth = sum widthsInChars
212+ let maxRowHeight = maximum $ map heightOfBlock (head:rows')
213+ let isMultilineTable = maxRowHeight > 1
214+ let underline = hsep $
215+ map (\width -> text $ replicate width '-') widthsInChars
216+ let border = if isMultilineTable
217+ then text $ replicate (sum widthsInChars + (length widthsInChars - 1)) '-'
218+ else empty
219+ let spacer = if isMultilineTable
220+ then text ""
221+ else empty
222+ let body = vcat $ intersperse spacer $ map blockToDoc rows'
223+ return $ (nest 2 $ border $+$ (blockToDoc head) $+$ underline $+$ body $+$
224+ border $+$ caption'') <> text "\n"
225+blockToMarkdown opts (BulletList items) = do
226+ contents <- mapM (bulletListItemToMarkdown opts) items
227+ return $ (vcat contents) <> text "\n"
228+blockToMarkdown opts (OrderedList attribs items) = do
229+ let markers = orderedListMarkers attribs
230+ let markers' = map (\m -> if length m < 3
231+ then m ++ replicate (3 - length m) ' '
232+ else m) markers
233+ contents <- mapM (\(item, num) -> orderedListItemToMarkdown opts item num) $
234+ zip markers' items
235+ return $ (vcat contents) <> text "\n"
236+blockToMarkdown opts (DefinitionList items) = do
237+ contents <- mapM (definitionListItemToMarkdown opts) items
238+ return $ (vcat contents) <> text "\n"
239+
240+-- | Convert bullet list item (list of blocks) to markdown.
241+bulletListItemToMarkdown :: WriterOptions -> [Block] -> State WriterState Doc
242+bulletListItemToMarkdown opts items = do
243+ contents <- blockListToMarkdown opts items
244+ return $ hang (text "- ") (writerTabStop opts) contents
245+
246+-- | Convert ordered list item (a list of blocks) to markdown.
247+orderedListItemToMarkdown :: WriterOptions -- ^ options
248+ -> String -- ^ list item marker
249+ -> [Block] -- ^ list item (list of blocks)
250+ -> State WriterState Doc
251+orderedListItemToMarkdown opts marker items = do
252+ contents <- blockListToMarkdown opts items
253+ -- The complexities here are needed to ensure that if the list
254+ -- marker is 4 characters or longer, the second and following
255+ -- lines are indented 4 spaces but the list item begins after the marker.
256+ return $ sep [nest (min (3 - length marker) 0) (text marker),
257+ nest (writerTabStop opts) contents]
258+
259+-- | Convert definition list item (label, list of blocks) to markdown.
260+definitionListItemToMarkdown :: WriterOptions
261+ -> ([Inline],[Block])
262+ -> State WriterState Doc
263+definitionListItemToMarkdown opts (label, items) = do
264+ labelText <- inlineListToMarkdown opts label
265+ let tabStop = writerTabStop opts
266+ let leader = char ':'
267+ contents <- mapM (\item -> blockToMarkdown opts item >>=
268+ (\txt -> return (leader $$ nest tabStop txt)))
269+ items >>= return . vcat
270+ return $ labelText $+$ contents
271+
272+-- | Convert list of Pandoc block elements to markdown.
273+blockListToMarkdown :: WriterOptions -- ^ Options
274+ -> [Block] -- ^ List of block elements
275+ -> State WriterState Doc
276+blockListToMarkdown opts blocks =
277+ mapM (blockToMarkdown opts) blocks >>= return . vcat
278+
279+-- | Get reference for target; if none exists, create unique one and return.
280+-- Prefer label if possible; otherwise, generate a unique key.
281+getReference :: [Inline] -> Target -> State WriterState [Inline]
282+getReference label (src, tit) = do
283+ (_,refs) <- get
284+ case find ((== (src, tit)) . snd) refs of
285+ Just (ref, _) -> return ref
286+ Nothing -> do
287+ let label' = case find ((== label) . fst) refs of
288+ Just _ -> -- label is used; generate numerical label
289+ case find (\n -> not (any (== [Str (show n)])
290+ (map fst refs))) [1..10000] of
291+ Just x -> [Str (show x)]
292+ Nothing -> error "no unique label"
293+ Nothing -> label
294+ modify (\(notes, refs) -> (notes, (label', (src,tit)):refs))
295+ return label'
296+
297+-- | Convert list of Pandoc inline elements to markdown.
298+inlineListToMarkdown :: WriterOptions -> [Inline] -> State WriterState Doc
299+inlineListToMarkdown opts lst =
300+ mapM (inlineToMarkdown opts) lst >>= return . hcat
301+
302+-- | Convert Pandoc inline element to markdown.
303+inlineToMarkdown :: WriterOptions -> Inline -> State WriterState Doc
304+inlineToMarkdown opts (Emph lst) = do
305+ contents <- inlineListToMarkdown opts lst
306+ return $ char '*' <> contents <> char '*'
307+inlineToMarkdown opts (Strong lst) = do
308+ contents <- inlineListToMarkdown opts lst
309+ return $ text "**" <> contents <> text "**"
310+inlineToMarkdown opts (Strikeout lst) = do
311+ contents <- inlineListToMarkdown opts lst
312+ return $ text "~~" <> contents <> text "~~"
313+inlineToMarkdown opts (Superscript lst) = do
314+ contents <- inlineListToMarkdown opts lst
315+ let contents' = text $ substitute " " "\\ " $ render contents
316+ return $ char '^' <> contents' <> char '^'
317+inlineToMarkdown opts (Subscript lst) = do
318+ contents <- inlineListToMarkdown opts lst
319+ let contents' = text $ substitute " " "\\ " $ render contents
320+ return $ char '~' <> contents' <> char '~'
321+inlineToMarkdown opts (Quoted SingleQuote lst) = do
322+ contents <- inlineListToMarkdown opts lst
323+ return $ char '\'' <> contents <> char '\''
324+inlineToMarkdown opts (Quoted DoubleQuote lst) = do
325+ contents <- inlineListToMarkdown opts lst
326+ return $ char '"' <> contents <> char '"'
327+inlineToMarkdown opts EmDash = return $ text "--"
328+inlineToMarkdown opts EnDash = return $ char '-'
329+inlineToMarkdown opts Apostrophe = return $ char '\''
330+inlineToMarkdown opts Ellipses = return $ text "..."
331+inlineToMarkdown opts (Code str) =
332+ let tickGroups = filter (\s -> '`' `elem` s) $ group str
333+ longest = if null tickGroups
334+ then 0
335+ else maximum $ map length tickGroups
336+ marker = replicate (longest + 1) '`'
337+ spacer = if (longest == 0) then "" else " " in
338+ return $ text (marker ++ spacer ++ str ++ spacer ++ marker)
339+inlineToMarkdown opts (Str str) = return $ text $ escapeString str
340+inlineToMarkdown opts (TeX str) = return $ text str
341+inlineToMarkdown opts (HtmlInline str) = return $ text str
342+inlineToMarkdown opts (LineBreak) = return $ text " \n"
343+inlineToMarkdown opts Space = return $ char ' '
344+inlineToMarkdown opts (Link txt (src, tit)) = do
345+ linktext <- inlineListToMarkdown opts txt
346+ let linktitle = if null tit then empty else text $ " \"" ++ tit ++ "\""
347+ let srcSuffix = if isPrefixOf "mailto:" src then drop 7 src else src
348+ let useRefLinks = writerReferenceLinks opts
349+ let useAuto = null tit && txt == [Code srcSuffix]
350+ ref <- if useRefLinks then getReference txt (src, tit) else return []
351+ reftext <- inlineListToMarkdown opts ref
352+ return $ if useAuto
353+ then char '<' <> text srcSuffix <> char '>'
354+ else if useRefLinks
355+ then let first = char '[' <> linktext <> char ']'
356+ second = if txt == ref
357+ then text "[]"
358+ else char '[' <> reftext <> char ']'
359+ in first <> second
360+ else char '[' <> linktext <> char ']' <>
361+ char '(' <> text src <> linktitle <> char ')'
362+inlineToMarkdown opts (Image alternate (source, tit)) = do
363+ let txt = if (null alternate) || (alternate == [Str ""]) ||
364+ (alternate == [Str source]) -- to prevent autolinks
365+ then [Str "image"]
366+ else alternate
367+ linkPart <- inlineToMarkdown opts (Link txt (source, tit))
368+ return $ char '!' <> linkPart
369+inlineToMarkdown opts (Note contents) = do
370+ modify (\(notes, refs) -> (contents:notes, refs)) -- add to notes in state
371+ (notes, _) <- get
372+ let ref = show $ (length notes)
373+ return $ text "[^" <> text ref <> char ']'
src/Text/Pandoc/Writers/RST.hsnew file+325
@@ -0,0 +1,325 @@
1+{-
2+Copyright (C) 2006-7 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.RST
21+ Copyright : Copyright (C) 2006-7 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 reStructuredText.
29+
30+reStructuredText: <http://docutils.sourceforge.net/rst.html>
31+-}
32+module Text.Pandoc.Writers.RST ( writeRST) where
33+import Text.Pandoc.Definition
34+import Text.Pandoc.Shared
35+import Text.Pandoc.Blocks
36+import Data.List ( isPrefixOf, isSuffixOf, drop, intersperse )
37+import Text.PrettyPrint.HughesPJ hiding ( Str )
38+import Control.Monad.State
39+
40+type Notes = [[Block]]
41+type Refs = KeyTable
42+type WriterState = (Notes, Refs, Refs) -- first Refs is links, second pictures
43+
44+-- | Convert Pandoc to RST.
45+writeRST :: WriterOptions -> Pandoc -> String
46+writeRST opts document =
47+ render $ evalState (pandocToRST opts document) ([],[],[])
48+
49+-- | Return RST representation of document.
50+pandocToRST :: WriterOptions -> Pandoc -> State WriterState Doc
51+pandocToRST opts (Pandoc meta blocks) = do
52+ let before = writerIncludeBefore opts
53+ let after = writerIncludeAfter opts
54+ before' = if null before then empty else text before
55+ after' = if null after then empty else text after
56+ metaBlock <- metaToRST opts meta
57+ let head = if (writerStandalone opts)
58+ then metaBlock $+$ text (writerHeader opts)
59+ else empty
60+ body <- blockListToRST opts blocks
61+ (notes, _, _) <- get
62+ notes' <- notesToRST opts (reverse notes)
63+ (_, refs, pics) <- get -- note that the notes may contain refs
64+ refs' <- keyTableToRST opts (reverse refs)
65+ pics' <- pictTableToRST opts (reverse pics)
66+ return $ head $+$ before' $+$ body $+$ notes' $+$ text "" $+$ refs' $+$
67+ pics' $+$ after'
68+
69+-- | Return RST representation of reference key table.
70+keyTableToRST :: WriterOptions -> KeyTable -> State WriterState Doc
71+keyTableToRST opts refs = mapM (keyToRST opts) refs >>= return . vcat
72+
73+-- | Return RST representation of a reference key.
74+keyToRST :: WriterOptions
75+ -> ([Inline], (String, String))
76+ -> State WriterState Doc
77+keyToRST opts (label, (src, tit)) = do
78+ label' <- inlineListToRST opts label
79+ let label'' = if ':' `elem` (render label')
80+ then char '`' <> label' <> char '`'
81+ else label'
82+ return $ text ".. _" <> label'' <> text ": " <> text src
83+
84+-- | Return RST representation of notes.
85+notesToRST :: WriterOptions -> [[Block]] -> State WriterState Doc
86+notesToRST opts notes =
87+ mapM (\(num, note) -> noteToRST opts num note) (zip [1..] notes) >>=
88+ return . vcat
89+
90+-- | Return RST representation of a note.
91+noteToRST :: WriterOptions -> Int -> [Block] -> State WriterState Doc
92+noteToRST opts num note = do
93+ contents <- blockListToRST opts note
94+ let marker = text ".. [" <> text (show num) <> text "] "
95+ return $ hang marker 3 contents
96+
97+-- | Return RST representation of picture reference table.
98+pictTableToRST :: WriterOptions -> KeyTable -> State WriterState Doc
99+pictTableToRST opts refs = mapM (pictToRST opts) refs >>= return . vcat
100+
101+-- | Return RST representation of a picture substitution reference.
102+pictToRST :: WriterOptions
103+ -> ([Inline], (String, String))
104+ -> State WriterState Doc
105+pictToRST opts (label, (src, _)) = do
106+ label' <- inlineListToRST opts label
107+ return $ text ".. " <> char '|' <> label' <> char '|' <> text " image:: " <>
108+ text src
109+
110+-- | Take list of inline elements and return wrapped doc.
111+wrappedRST :: WriterOptions -> [Inline] -> State WriterState Doc
112+wrappedRST opts inlines = mapM (wrapIfNeeded opts (inlineListToRST opts))
113+ (splitBy LineBreak inlines) >>= return . vcat
114+
115+-- | Escape special characters for RST.
116+escapeString :: String -> String
117+escapeString = escapeStringUsing (backslashEscapes "`\\|*_")
118+
119+-- | Convert bibliographic information into RST header.
120+metaToRST :: WriterOptions -> Meta -> State WriterState Doc
121+metaToRST opts (Meta title authors date) = do
122+ title' <- titleToRST opts title
123+ authors' <- authorsToRST authors
124+ date' <- dateToRST date
125+ let toc = if writerTableOfContents opts
126+ then text "" $+$ text ".. contents::"
127+ else empty
128+ return $ title' $+$ authors' $+$ date' $+$ toc
129+
130+titleToRST :: WriterOptions -> [Inline] -> State WriterState Doc
131+titleToRST opts [] = return empty
132+titleToRST opts lst = do
133+ contents <- inlineListToRST opts lst
134+ let titleLength = length $ render contents
135+ let border = text (replicate titleLength '=')
136+ return $ border $+$ contents $+$ border <> text "\n"
137+
138+authorsToRST :: [String] -> State WriterState Doc
139+authorsToRST [] = return empty
140+authorsToRST (first:rest) = do
141+ rest' <- authorsToRST rest
142+ return $ (text ":Author: " <> text first) $+$ rest'
143+
144+dateToRST :: String -> State WriterState Doc
145+dateToRST [] = return empty
146+dateToRST str = return $ text ":Date: " <> text (escapeString str)
147+
148+-- | Convert Pandoc block element to RST.
149+blockToRST :: WriterOptions -- ^ Options
150+ -> Block -- ^ Block element
151+ -> State WriterState Doc
152+blockToRST opts Null = return empty
153+blockToRST opts (Plain inlines) = wrappedRST opts inlines
154+blockToRST opts (Para [TeX str]) =
155+ let str' = if "\n" `isSuffixOf` str then str ++ "\n" else str ++ "\n\n" in
156+ return $ hang (text "\n.. raw:: latex\n") 3 $ vcat $ map text (lines str')
157+blockToRST opts (Para inlines) = do
158+ contents <- wrappedRST opts inlines
159+ return $ contents <> text "\n"
160+blockToRST opts (RawHtml str) =
161+ let str' = if "\n" `isSuffixOf` str then str ++ "\n" else str ++ "\n\n" in
162+ return $ hang (text "\n.. raw:: html\n") 3 $ vcat $ map text (lines str')
163+blockToRST opts HorizontalRule = return $ text "--------------\n"
164+blockToRST opts (Header level inlines) = do
165+ contents <- inlineListToRST opts inlines
166+ let headerLength = length $ render contents
167+ let headerChar = if level > 5 then ' ' else "=-~^'" !! (level - 1)
168+ let border = text $ replicate headerLength headerChar
169+ return $ contents $+$ border <> text "\n"
170+blockToRST opts (CodeBlock str) = return $ (text "::\n") $+$
171+ (nest (writerTabStop opts) $ vcat $ map text (lines str)) <> text "\n"
172+blockToRST opts (BlockQuote blocks) = do
173+ contents <- blockListToRST opts blocks
174+ return $ (nest (writerTabStop opts) contents) <> text "\n"
175+blockToRST opts (Table caption aligns widths headers rows) = do
176+ caption' <- inlineListToRST opts caption
177+ let caption'' = if null caption
178+ then empty
179+ else text "" $+$ (text "Table: " <> caption')
180+ headers' <- mapM (blockListToRST opts) headers
181+ let widthsInChars = map (floor . (78 *)) widths
182+ let alignHeader alignment = case alignment of
183+ AlignLeft -> leftAlignBlock
184+ AlignCenter -> centerAlignBlock
185+ AlignRight -> rightAlignBlock
186+ AlignDefault -> leftAlignBlock
187+ let hpipeBlocks blocks = hcatBlocks [beg, middle, end]
188+ where height = maximum (map heightOfBlock blocks)
189+ sep = TextBlock 3 height (replicate height " | ")
190+ beg = TextBlock 2 height (replicate height "| ")
191+ end = TextBlock 2 height (replicate height " |")
192+ middle = hcatBlocks $ intersperse sep blocks
193+ let makeRow = hpipeBlocks . zipWith docToBlock widthsInChars
194+ let head = makeRow headers'
195+ rows' <- mapM (\row -> do cols <- mapM (blockListToRST opts) row
196+ return $ makeRow cols) rows
197+ let tableWidth = sum widthsInChars
198+ let maxRowHeight = maximum $ map heightOfBlock (head:rows')
199+ let border ch = char '+' <> char ch <>
200+ (hcat $ intersperse (char ch <> char '+' <> char ch) $
201+ map (\l -> text $ replicate l ch) widthsInChars) <>
202+ char ch <> char '+'
203+ let body = vcat $ intersperse (border '-') $ map blockToDoc rows'
204+ return $ border '-' $+$ blockToDoc head $+$ border '=' $+$ body $+$
205+ border '-' $$ caption'' $$ text ""
206+blockToRST opts (BulletList items) = do
207+ contents <- mapM (bulletListItemToRST opts) items
208+ -- ensure that sublists have preceding blank line
209+ return $ text "" $+$ vcat contents <> text "\n"
210+blockToRST opts (OrderedList (start, style, delim) items) = do
211+ let markers = if start == 1 && style == DefaultStyle && delim == DefaultDelim
212+ then take (length items) $ repeat "#."
213+ else take (length items) $ orderedListMarkers
214+ (start, style, delim)
215+ let maxMarkerLength = maximum $ map length markers
216+ let markers' = map (\m -> let s = maxMarkerLength - length m
217+ in m ++ replicate s ' ') markers
218+ contents <- mapM (\(item, num) -> orderedListItemToRST opts item num) $
219+ zip markers' items
220+ -- ensure that sublists have preceding blank line
221+ return $ text "" $+$ vcat contents <> text "\n"
222+blockToRST opts (DefinitionList items) = do
223+ contents <- mapM (definitionListItemToRST opts) items
224+ return $ (vcat contents) <> text "\n"
225+
226+-- | Convert bullet list item (list of blocks) to RST.
227+bulletListItemToRST :: WriterOptions -> [Block] -> State WriterState Doc
228+bulletListItemToRST opts items = do
229+ contents <- blockListToRST opts items
230+ return $ hang (text "- ") 3 contents
231+
232+-- | Convert ordered list item (a list of blocks) to RST.
233+orderedListItemToRST :: WriterOptions -- ^ options
234+ -> String -- ^ marker for list item
235+ -> [Block] -- ^ list item (list of blocks)
236+ -> State WriterState Doc
237+orderedListItemToRST opts marker items = do
238+ contents <- blockListToRST opts items
239+ return $ hang (text marker) (length marker + 1) contents
240+
241+-- | Convert defintion list item (label, list of blocks) to RST.
242+definitionListItemToRST :: WriterOptions -> ([Inline], [Block]) -> State WriterState Doc
243+definitionListItemToRST opts (label, items) = do
244+ label <- inlineListToRST opts label
245+ contents <- blockListToRST opts items
246+ return $ label $+$ nest (writerTabStop opts) contents
247+
248+-- | Convert list of Pandoc block elements to RST.
249+blockListToRST :: WriterOptions -- ^ Options
250+ -> [Block] -- ^ List of block elements
251+ -> State WriterState Doc
252+blockListToRST opts blocks =
253+ mapM (blockToRST opts) blocks >>= return . vcat
254+
255+-- | Convert list of Pandoc inline elements to RST.
256+inlineListToRST :: WriterOptions -> [Inline] -> State WriterState Doc
257+inlineListToRST opts lst = mapM (inlineToRST opts) lst >>= return . hcat
258+
259+-- | Convert Pandoc inline element to RST.
260+inlineToRST :: WriterOptions -> Inline -> State WriterState Doc
261+inlineToRST opts (Emph lst) = do
262+ contents <- inlineListToRST opts lst
263+ return $ char '*' <> contents <> char '*'
264+inlineToRST opts (Strong lst) = do
265+ contents <- inlineListToRST opts lst
266+ return $ text "**" <> contents <> text "**"
267+inlineToRST opts (Strikeout lst) = do
268+ contents <- inlineListToRST opts lst
269+ return $ text "[STRIKEOUT:" <> contents <> char ']'
270+inlineToRST opts (Superscript lst) = do
271+ contents <- inlineListToRST opts lst
272+ return $ text "\\ :sup:`" <> contents <> text "`\\ "
273+inlineToRST opts (Subscript lst) = do
274+ contents <- inlineListToRST opts lst
275+ return $ text "\\ :sub:`" <> contents <> text "`\\ "
276+inlineToRST opts (Quoted SingleQuote lst) = do
277+ contents <- inlineListToRST opts lst
278+ return $ char '\'' <> contents <> char '\''
279+inlineToRST opts (Quoted DoubleQuote lst) = do
280+ contents <- inlineListToRST opts lst
281+ return $ char '"' <> contents <> char '"'
282+inlineToRST opts EmDash = return $ text "--"
283+inlineToRST opts EnDash = return $ char '-'
284+inlineToRST opts Apostrophe = return $ char '\''
285+inlineToRST opts Ellipses = return $ text "..."
286+inlineToRST opts (Code str) = return $ text $ "``" ++ str ++ "``"
287+inlineToRST opts (Str str) = return $ text $ escapeString str
288+inlineToRST opts (TeX str) = return $ text str
289+inlineToRST opts (HtmlInline str) = return empty
290+inlineToRST opts (LineBreak) = return $ char ' ' -- RST doesn't have linebreaks
291+inlineToRST opts Space = return $ char ' '
292+inlineToRST opts (Link [Code str] (src, tit)) | src == str ||
293+ src == "mailto:" ++ str = do
294+ let srcSuffix = if isPrefixOf "mailto:" src then drop 7 src else src
295+ return $ text srcSuffix
296+inlineToRST opts (Link txt (src, tit)) = do
297+ let useReferenceLinks = writerReferenceLinks opts
298+ linktext <- inlineListToRST opts $ normalizeSpaces txt
299+ if useReferenceLinks
300+ then do (notes, refs, pics) <- get
301+ let refs' = if (txt, (src, tit)) `elem` refs
302+ then refs
303+ else (txt, (src, tit)):refs
304+ put (notes, refs', pics)
305+ return $ char '`' <> linktext <> text "`_"
306+ else return $ char '`' <> linktext <> text " <" <> text src <> text ">`_"
307+inlineToRST opts (Image alternate (source, tit)) = do
308+ (notes, refs, pics) <- get
309+ let labelsUsed = map fst pics
310+ let txt = if null alternate || alternate == [Str ""] ||
311+ alternate `elem` labelsUsed
312+ then [Str $ "image" ++ show (length refs)]
313+ else alternate
314+ let pics' = if (txt, (source, tit)) `elem` pics
315+ then pics
316+ else (txt, (source, tit)):pics
317+ put (notes, refs, pics')
318+ label <- inlineListToRST opts txt
319+ return $ char '|' <> label <> char '|'
320+inlineToRST opts (Note contents) = do
321+ -- add to notes in state
322+ modify (\(notes, refs, pics) -> (contents:notes, refs, pics))
323+ (notes, _, _) <- get
324+ let ref = show $ (length notes)
325+ return $ text " [" <> text ref <> text "]_"
src/Text/Pandoc/Writers/RTF.hsnew file+286
@@ -0,0 +1,286 @@
1+{-
2+Copyright (C) 2006-7 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.RTF
21+ Copyright : Copyright (C) 2006-7 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 RTF (rich text format).
29+-}
30+module Text.Pandoc.Writers.RTF ( writeRTF ) where
31+import Text.Pandoc.Definition
32+import Text.Pandoc.Shared
33+import Text.Regex ( matchRegexAll, mkRegex )
34+import Data.List ( isSuffixOf )
35+import Data.Char ( ord )
36+
37+-- | Convert Pandoc to a string in rich text format.
38+writeRTF :: WriterOptions -> Pandoc -> String
39+writeRTF options (Pandoc meta blocks) =
40+ let head = if writerStandalone options
41+ then rtfHeader (writerHeader options) meta
42+ else ""
43+ toc = if writerTableOfContents options
44+ then tableOfContents $ filter isHeaderBlock blocks
45+ else ""
46+ foot = if writerStandalone options then "\n}\n" else ""
47+ body = writerIncludeBefore options ++
48+ concatMap (blockToRTF 0 AlignDefault) blocks ++
49+ writerIncludeAfter options
50+ in head ++ toc ++ body ++ foot
51+
52+-- | Construct table of contents from list of header blocks.
53+tableOfContents :: [Block] -> String
54+tableOfContents headers =
55+ let contentsTree = hierarchicalize headers
56+ in concatMap (blockToRTF 0 AlignDefault) $
57+ [Header 1 [Str "Contents"],
58+ BulletList (map elementToListItem contentsTree)]
59+
60+elementToListItem :: Element -> [Block]
61+elementToListItem (Blk _) = []
62+elementToListItem (Sec sectext subsecs) = [Plain sectext] ++
63+ if null subsecs
64+ then []
65+ else [BulletList (map elementToListItem subsecs)]
66+
67+-- | Convert unicode characters (> 127) into rich text format representation.
68+handleUnicode :: String -> String
69+handleUnicode [] = []
70+handleUnicode (c:cs) =
71+ if ord c > 127
72+ then '\\':'u':(show (ord c)) ++ "?" ++ handleUnicode cs
73+ else c:(handleUnicode cs)
74+
75+-- | Escape special characters.
76+escapeSpecial :: String -> String
77+escapeSpecial = escapeStringUsing (('\t',"\\tab "):(backslashEscapes "{\\}"))
78+
79+-- | Escape strings as needed for rich text format.
80+stringToRTF :: String -> String
81+stringToRTF = handleUnicode . escapeSpecial
82+
83+-- | Escape things as needed for code block in RTF.
84+codeStringToRTF :: String -> String
85+codeStringToRTF str = joinWithSep "\\line\n" $ lines (stringToRTF str)
86+
87+-- | Deal with raw LaTeX.
88+latexToRTF :: String -> String
89+latexToRTF str = "{\\cf1 " ++ (stringToRTF str) ++ "\\cf0 } "
90+
91+-- | Make a paragraph with first-line indent, block indent, and space after.
92+rtfParSpaced :: Int -- ^ space after (in twips)
93+ -> Int -- ^ block indent (in twips)
94+ -> Int -- ^ first line indent (relative to block) (in twips)
95+ -> Alignment -- ^ alignment
96+ -> String -- ^ string with content
97+ -> String
98+rtfParSpaced spaceAfter indent firstLineIndent alignment content =
99+ let alignString = case alignment of
100+ AlignLeft -> "\\ql "
101+ AlignRight -> "\\qr "
102+ AlignCenter -> "\\qc "
103+ AlignDefault -> "\\ql "
104+ in "{\\pard " ++ alignString ++
105+ "\\f0 \\sa" ++ (show spaceAfter) ++ " \\li" ++ (show indent) ++
106+ " \\fi" ++ (show firstLineIndent) ++ " " ++ content ++ "\\par}\n"
107+
108+-- | Default paragraph.
109+rtfPar :: Int -- ^ block indent (in twips)
110+ -> Int -- ^ first line indent (relative to block) (in twips)
111+ -> Alignment -- ^ alignment
112+ -> String -- ^ string with content
113+ -> String
114+rtfPar = rtfParSpaced 180
115+
116+-- | Compact paragraph (e.g. for compact list items).
117+rtfCompact :: Int -- ^ block indent (in twips)
118+ -> Int -- ^ first line indent (relative to block) (in twips)
119+ -> Alignment -- ^ alignment
120+ -> String -- ^ string with content
121+ -> String
122+rtfCompact = rtfParSpaced 0
123+
124+-- number of twips to indent
125+indentIncrement = 720
126+listIncrement = 360
127+
128+-- | Returns appropriate bullet list marker for indent level.
129+bulletMarker :: Int -> String
130+bulletMarker indent = case indent `mod` 720 of
131+ 0 -> "\\bullet "
132+ otherwise -> "\\endash "
133+
134+-- | Returns appropriate (list of) ordered list markers for indent level.
135+orderedMarkers :: Int -> ListAttributes -> [String]
136+orderedMarkers indent (start, style, delim) =
137+ if style == DefaultStyle && delim == DefaultDelim
138+ then case indent `mod` 720 of
139+ 0 -> orderedListMarkers (start, Decimal, Period)
140+ otherwise -> orderedListMarkers (start, LowerAlpha, Period)
141+ else orderedListMarkers (start, style, delim)
142+
143+-- | Returns RTF header.
144+rtfHeader :: String -- ^ header text
145+ -> Meta -- ^ bibliographic information
146+ -> String
147+rtfHeader headerText (Meta title authors date) =
148+ let titletext = if null title
149+ then ""
150+ else rtfPar 0 0 AlignCenter $
151+ "\\b \\fs36 " ++ inlineListToRTF title
152+ authorstext = if null authors
153+ then ""
154+ else rtfPar 0 0 AlignCenter (" " ++ (joinWithSep "\\" $
155+ map stringToRTF authors))
156+ datetext = if date == ""
157+ then ""
158+ else rtfPar 0 0 AlignCenter (" " ++ stringToRTF date) in
159+ let spacer = if null (titletext ++ authorstext ++ datetext)
160+ then ""
161+ else rtfPar 0 0 AlignDefault "" in
162+ headerText ++ titletext ++ authorstext ++ datetext ++ spacer
163+
164+-- | Convert Pandoc block element to RTF.
165+blockToRTF :: Int -- ^ indent level
166+ -> Alignment -- ^ alignment
167+ -> Block -- ^ block to convert
168+ -> String
169+blockToRTF _ _ Null = ""
170+blockToRTF indent alignment (Plain lst) =
171+ rtfCompact indent 0 alignment $ inlineListToRTF lst
172+blockToRTF indent alignment (Para lst) =
173+ rtfPar indent 0 alignment $ inlineListToRTF lst
174+blockToRTF indent alignment (BlockQuote lst) =
175+ concatMap (blockToRTF (indent + indentIncrement) alignment) lst
176+blockToRTF indent _ (CodeBlock str) =
177+ rtfPar indent 0 AlignLeft ("\\f1 " ++ (codeStringToRTF str))
178+blockToRTF _ _ (RawHtml str) = ""
179+blockToRTF indent alignment (BulletList lst) = spaceAtEnd $
180+ concatMap (listItemToRTF alignment indent (bulletMarker indent)) lst
181+blockToRTF indent alignment (OrderedList attribs lst) = spaceAtEnd $ concat $
182+ zipWith (listItemToRTF alignment indent) (orderedMarkers indent attribs) lst
183+blockToRTF indent alignment (DefinitionList lst) = spaceAtEnd $
184+ concatMap (definitionListItemToRTF alignment indent) lst
185+blockToRTF indent _ HorizontalRule =
186+ rtfPar indent 0 AlignCenter "\\emdash\\emdash\\emdash\\emdash\\emdash"
187+blockToRTF indent alignment (Header level lst) = rtfPar indent 0 alignment $
188+ "\\b \\fs" ++ (show (40 - (level * 4))) ++ " " ++ inlineListToRTF lst
189+blockToRTF indent alignment (Table caption aligns sizes headers rows) =
190+ tableRowToRTF True indent aligns sizes headers ++
191+ concatMap (tableRowToRTF False indent aligns sizes) rows ++
192+ rtfPar indent 0 alignment (inlineListToRTF caption)
193+
194+tableRowToRTF :: Bool -> Int -> [Alignment] -> [Float] -> [[Block]] -> String
195+tableRowToRTF header indent aligns sizes cols =
196+ let columns = concat $ zipWith (tableItemToRTF indent) aligns cols
197+ totalTwips = 6 * 1440 -- 6 inches
198+ rightEdges = tail $ scanl (\sofar new -> sofar + floor (new * totalTwips))
199+ 0 sizes
200+ cellDefs = map (\edge -> (if header
201+ then "\\clbrdrb\\brdrs"
202+ else "") ++ "\\cellx" ++ show edge)
203+ rightEdges
204+ start = "{\n\\trowd \\trgaph120\n" ++ concat cellDefs ++ "\n" ++
205+ "\\trkeep\\intbl\n{\n"
206+ end = "}\n\\intbl\\row}\n"
207+ in start ++ columns ++ end
208+
209+tableItemToRTF :: Int -> Alignment -> [Block] -> String
210+tableItemToRTF indent alignment item =
211+ let contents = concatMap (blockToRTF indent alignment) item
212+ in "{\\intbl " ++ contents ++ "\\cell}\n"
213+
214+-- | Ensure that there's the same amount of space after compact
215+-- lists as after regular lists.
216+spaceAtEnd :: String -> String
217+spaceAtEnd str =
218+ if isSuffixOf "\\par}\n" str
219+ then (take ((length str) - 6) str) ++ "\\sa180\\par}\n"
220+ else str
221+
222+-- | Convert list item (list of blocks) to RTF.
223+listItemToRTF :: Alignment -- ^ alignment
224+ -> Int -- ^ indent level
225+ -> String -- ^ list start marker
226+ -> [Block] -- ^ list item (list of blocks)
227+ -> [Char]
228+listItemToRTF alignment indent marker [] =
229+ rtfCompact (indent + listIncrement) (0 - listIncrement) alignment
230+ (marker ++ "\\tx" ++ (show listIncrement) ++ "\\tab ")
231+listItemToRTF alignment indent marker list =
232+ let (first:rest) = map (blockToRTF (indent + listIncrement) alignment) list in
233+ -- insert the list marker into the (processed) first block
234+ let modFirst = case matchRegexAll (mkRegex "\\\\fi-?[0-9]+") first of
235+ Just (before, matched, after, _) ->
236+ before ++ "\\fi" ++ show (0 - listIncrement) ++
237+ " " ++ marker ++ "\\tx" ++
238+ show listIncrement ++ "\\tab" ++ after
239+ Nothing -> first in
240+ modFirst ++ concat rest
241+
242+-- | Convert definition list item (label, list of blocks) to RTF.
243+definitionListItemToRTF :: Alignment -- ^ alignment
244+ -> Int -- ^ indent level
245+ -> ([Inline],[Block]) -- ^ list item (list of blocks)
246+ -> [Char]
247+definitionListItemToRTF alignment indent (label, items) =
248+ let labelText = blockToRTF indent alignment (Plain label)
249+ itemsText = concatMap (blockToRTF (indent + listIncrement) alignment) items
250+ in labelText ++ itemsText
251+
252+-- | Convert list of inline items to RTF.
253+inlineListToRTF :: [Inline] -- ^ list of inlines to convert
254+ -> String
255+inlineListToRTF lst = concatMap inlineToRTF lst
256+
257+-- | Convert inline item to RTF.
258+inlineToRTF :: Inline -- ^ inline to convert
259+ -> String
260+inlineToRTF (Emph lst) = "{\\i " ++ (inlineListToRTF lst) ++ "} "
261+inlineToRTF (Strong lst) = "{\\b " ++ (inlineListToRTF lst) ++ "} "
262+inlineToRTF (Strikeout lst) = "{\\strike " ++ (inlineListToRTF lst) ++ "} "
263+inlineToRTF (Superscript lst) = "{\\super " ++ (inlineListToRTF lst) ++ "} "
264+inlineToRTF (Subscript lst) = "{\\sub " ++ (inlineListToRTF lst) ++ "} "
265+inlineToRTF (Quoted SingleQuote lst) =
266+ "\\u8216'" ++ (inlineListToRTF lst) ++ "\\u8217'"
267+inlineToRTF (Quoted DoubleQuote lst) =
268+ "\\u8220\"" ++ (inlineListToRTF lst) ++ "\\u8221\""
269+inlineToRTF Apostrophe = "\\u8217'"
270+inlineToRTF Ellipses = "\\u8230?"
271+inlineToRTF EmDash = "\\u8212-"
272+inlineToRTF EnDash = "\\u8211-"
273+inlineToRTF (Code str) = "{\\f1 " ++ (codeStringToRTF str) ++ "} "
274+inlineToRTF (Str str) = stringToRTF str
275+inlineToRTF (TeX str) = latexToRTF str
276+inlineToRTF (HtmlInline str) = ""
277+inlineToRTF (LineBreak) = "\\line "
278+inlineToRTF Space = " "
279+inlineToRTF (Link text (src, tit)) =
280+ "{\\field{\\*\\fldinst{HYPERLINK \"" ++ (codeStringToRTF src) ++
281+ "\"}}{\\fldrslt{\\ul\n" ++ (inlineListToRTF text) ++ "\n}}}\n"
282+inlineToRTF (Image alternate (source, tit)) =
283+ "{\\cf1 [image: " ++ source ++ "]\\cf0}"
284+inlineToRTF (Note contents) =
285+ "{\\super\\chftn}{\\*\\footnote\\chftn\\~\\plain\\pard " ++
286+ (concatMap (blockToRTF 0 AlignDefault) contents) ++ "}"
data/headers/ConTeXtHeader → src/headers/ConTeXtHeaderrenamed

The content is the same.

data/headers/DocbookHeader → src/headers/DocbookHeaderrenamed

The content is the same.

data/headers/LaTeXHeader → src/headers/LaTeXHeaderrenamed

The content is the same.

data/headers/RTFHeader → src/headers/RTFHeaderrenamed

The content is the same.

data/headers/S5Header → src/headers/S5Headerrenamed

The content is the same.

src/templates/ASCIIMathML.hsnew file+7
@@ -0,0 +1,7 @@
1+-- | Definitions for use of ASCIIMathML in HTML.
2+-- (See <http://www1.chapman.edu/~jipsen/mathml/asciimath.html>.)
3+module Text.Pandoc.ASCIIMathML ( asciiMathMLScript ) where
4+
5+-- | String containing ASCIIMathML javascript.
6+asciiMathMLScript :: String
7+asciiMathMLScript = "<script type=\"text/javascript\">\n@ASCIIMathML.js@</script>\n"
src/templates/DefaultHeaders.hsnew file+52
@@ -0,0 +1,52 @@
1+{-
2+Copyright (C) 2006-7 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.DefaultHeaders
21+ Copyright : Copyright (C) 2006-7 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+Default headers for Pandoc writers.
29+-}
30+module Text.Pandoc.Writers.DefaultHeaders (
31+ defaultLaTeXHeader,
32+ defaultConTeXtHeader,
33+ defaultDocbookHeader,
34+ defaultS5Header,
35+ defaultRTFHeader
36+ ) where
37+import Text.Pandoc.Writers.S5
38+
39+defaultLaTeXHeader :: String
40+defaultLaTeXHeader = "@LaTeXHeader@"
41+
42+defaultConTeXtHeader :: String
43+defaultConTeXtHeader = "@ConTeXtHeader@"
44+
45+defaultDocbookHeader :: String
46+defaultDocbookHeader = "@DocbookHeader@"
47+
48+defaultS5Header :: String
49+defaultS5Header = s5Meta ++ s5CSS ++ s5Javascript
50+
51+defaultRTFHeader :: String
52+defaultRTFHeader = "@RTFHeader@"
src/templates/Makefilenew file+20
@@ -0,0 +1,20 @@
1+VPATH := ..
2+PROCESSOR := ./fillTemplates.pl
3+TARGETS := Text/Pandoc/ASCIIMathML.hs \
4+ Text/Pandoc/Writers/S5.hs \
5+ Text/Pandoc/Writers/DefaultHeaders.hs
6+
7+all: $(TARGETS)
8+
9+Text/Pandoc/ASCIIMathML.hs: ASCIIMathML.hs $(PROCESSOR) $(VPATH)/ASCIIMathML.js
10+ perl $(PROCESSOR) $@ $(VPATH)
11+
12+Text/Pandoc/Writers/S5.hs: S5.hs $(PROCESSOR) $(VPATH)/ui/default/*
13+ perl $(PROCESSOR) $@ $(VPATH)
14+
15+Text/Pandoc/Writers/DefaultHeaders.hs: DefaultHeaders.hs $(PROCESSOR) $(VPATH)/headers/*
16+ perl $(PROCESSOR) $@ $(VPATH)
17+
18+.PHONY: clean
19+clean:
20+ cd $(VPATH); rm -f $(TARGETS)
src/templates/S5.hsnew file+133
@@ -0,0 +1,133 @@
1+{-
2+Copyright (C) 2006-7 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.S5
21+ Copyright : Copyright (C) 2006-7 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+Definitions for creation of S5 powerpoint-like HTML.
29+(See <http://meyerweb.com/eric/tools/s5/>.)
30+-}
31+module Text.Pandoc.Writers.S5 (
32+ -- * Strings
33+ s5Meta,
34+ s5Javascript,
35+ s5CSS,
36+ s5Links,
37+ -- * Functions
38+ writeS5,
39+ writeS5String,
40+ insertS5Structure
41+ ) where
42+import Text.Pandoc.Shared ( joinWithSep, WriterOptions )
43+import Text.Pandoc.Writers.HTML ( writeHtml, writeHtmlString )
44+import Text.Pandoc.Definition
45+import Text.XHtml.Strict
46+
47+s5Meta :: String
48+s5Meta = "<!-- configuration parameters -->\n<meta name=\"defaultView\" content=\"slideshow\" />\n<meta name=\"controlVis\" content=\"hidden\" />\n"
49+
50+s5Javascript :: String
51+s5Javascript = "<script type=\"text/javascript\">\n@slides.js@</script>\n"
52+
53+s5CoreCSS :: String
54+s5CoreCSS = "@s5-core.css@"
55+
56+s5FramingCSS :: String
57+s5FramingCSS = "@framing.css@"
58+
59+s5PrettyCSS :: String
60+s5PrettyCSS = "@pretty.css@"
61+
62+s5OperaCSS :: String
63+s5OperaCSS = "@opera.css@"
64+
65+s5OutlineCSS :: String
66+s5OutlineCSS = "@outline.css@"
67+
68+s5PrintCSS :: String
69+s5PrintCSS = "@print.css@"
70+
71+s5CSS :: String
72+s5CSS = "<style type=\"text/css\" media=\"projection\" id=\"slideProj\">\n" ++ s5CoreCSS ++ "\n" ++ s5FramingCSS ++ "\n" ++ s5PrettyCSS ++ "\n</style>\n<style type=\"text/css\" media=\"projection\" id=\"operaFix\">\n" ++ s5OperaCSS ++ "\n</style>\n<style type=\"text/css\" media=\"screen\" id=\"outlineStyle\">\n" ++ s5OutlineCSS ++ "\n</style>\n<style type=\"text/css\" media=\"print\" id=\"slidePrint\">\n" ++ s5PrintCSS ++ "\n</style>\n"
73+
74+s5Links :: String
75+s5Links = "<!-- style sheet links -->\n<link rel=\"stylesheet\" href=\"ui/default/slides.css\" type=\"text/css\" media=\"projection\" id=\"slideProj\" />\n<link rel=\"stylesheet\" href=\"ui/default/outline.css\" type=\"text/css\" media=\"screen\" id=\"outlineStyle\" />\n<link rel=\"stylesheet\" href=\"ui/default/print.css\" type=\"text/css\" media=\"print\" id=\"slidePrint\" />\n<link rel=\"stylesheet\" href=\"ui/default/opera.css\" type=\"text/css\" media=\"projection\" id=\"operaFix\" />\n<!-- S5 JS -->\n<script src=\"ui/default/slides.js\" type=\"text/javascript\"></script>\n"
76+
77+-- | Converts Pandoc document to an S5 HTML presentation (Html structure).
78+writeS5 :: WriterOptions -> Pandoc -> Html
79+writeS5 options = (writeHtml options) . insertS5Structure
80+
81+-- | Converts Pandoc document to an S5 HTML presentation (string).
82+writeS5String :: WriterOptions -> Pandoc -> String
83+writeS5String options = (writeHtmlString options) . insertS5Structure
84+
85+-- | Inserts HTML needed for an S5 presentation (e.g. around slides).
86+layoutDiv :: [Inline] -- ^ Title of document (for header or footer)
87+ -> String -- ^ Date of document (for header or footer)
88+ -> [Block] -- ^ List of block elements returned
89+layoutDiv title date = [(RawHtml "<div class=\"layout\">\n<div id=\"controls\"></div>\n<div id=\"currentSlide\"></div>\n<div id=\"header\"></div>\n<div id=\"footer\">\n"), (Header 1 [Str date]), (Header 2 title), (RawHtml "</div>\n</div>\n")]
90+
91+presentationStart = RawHtml "<div class=\"presentation\">\n\n"
92+
93+presentationEnd = RawHtml "</div>\n"
94+
95+slideStart = RawHtml "<div class=\"slide\">\n"
96+
97+slideEnd = RawHtml "</div>\n"
98+
99+-- | Returns 'True' if block is a Header 1.
100+isH1 :: Block -> Bool
101+isH1 (Header 1 _) = True
102+isH1 _ = False
103+
104+-- | Insert HTML around sections to make individual slides.
105+insertSlides :: Bool -> [Block] -> [Block]
106+insertSlides beginning blocks =
107+ let (beforeHead, rest) = break isH1 blocks in
108+ if (null rest) then
109+ if beginning then
110+ beforeHead
111+ else
112+ beforeHead ++ [slideEnd]
113+ else
114+ if beginning then
115+ beforeHead ++
116+ slideStart:(head rest):(insertSlides False (tail rest))
117+ else
118+ beforeHead ++
119+ slideEnd:slideStart:(head rest):(insertSlides False (tail rest))
120+
121+-- | Insert blocks into 'Pandoc' for slide structure.
122+insertS5Structure :: Pandoc -> Pandoc
123+insertS5Structure (Pandoc meta []) = Pandoc meta []
124+insertS5Structure (Pandoc (Meta title authors date) blocks) =
125+ let slides = insertSlides True blocks
126+ firstSlide = if not (null title)
127+ then [slideStart, (Header 1 title),
128+ (Header 3 [Str (joinWithSep ", " authors)]),
129+ (Header 4 [Str date]), slideEnd]
130+ else []
131+ newBlocks = (layoutDiv title date) ++ presentationStart:firstSlide ++
132+ slides ++ [presentationEnd]
133+ in Pandoc (Meta title authors date) newBlocks
src/templates/fillTemplates.plnew file+131
@@ -0,0 +1,131 @@
1+#!/usr/bin/env perl
2+# Fills templates with haskell-escaped strings slurped from input files.
3+# Takes two arguments, the first specifying the pathname of the target
4+# relative to the root directory, the second specifying the root directory
5+# (defaulting to ..). The template is assumed to have the same base name
6+# as the target and to reside in the templates subdirectory of the root
7+# directory.
8+
9+use strict;
10+use warnings;
11+
12+# Utility routines:
13+
14+sub slurp {
15+ open FILE, $_[0] or die "couldn't open file '$_[0]': $!";
16+ my $contents = do { local $/; <FILE>;};
17+ close FILE;
18+ return $contents;
19+}
20+
21+sub escape_for_haskell {
22+ my ($contents) = @_;
23+
24+ $contents =~ s/\\/\\\\/g;
25+ $contents =~ s/\t/\\t/g;
26+ $contents =~ s/"/\\"/g;
27+ $contents =~ s/\n/\\n/g;
28+ return $contents;
29+}
30+
31+# Template processors.
32+
33+my %processor = (
34+ # --------------------------------------------------------------------------
35+ 'Text/Pandoc/Writers/S5.hs' => {
36+ # --------------------------------------------------------------------------
37+ proc => sub {
38+ my ($template) = @_;
39+
40+ my (@files) = qw(slides.js s5-core.css framing.css pretty.css
41+ opera.css outline.css print.css);
42+
43+ foreach my $file (@files) {
44+ my $replacement = escape_for_haskell(slurp "ui/default/$file");
45+ my $escapedfile = $file;
46+ $escapedfile =~ s/\./\\./g;
47+ $template =~ s/\@$escapedfile\@/$replacement/;
48+ }
49+ return $template;
50+ },
51+ },
52+ # --------------------------------------------------------------------------
53+ 'Text/Pandoc/ASCIIMathML.hs' => {
54+ # --------------------------------------------------------------------------
55+ proc => sub {
56+ my ($template) = @_;
57+
58+ my $script = escape_for_haskell(slurp "ASCIIMathML.js");
59+ my $acknowledgements =
60+ " ASCIIMathML.js - copyright Peter Jipsen,".
61+ " released under the GPL\\nSee ".
62+ "http://www1.chapman.edu/~jipsen/mathml/asciimath.html/ ";
63+ $script =~ s/\/\*.*?\*\//\/\*$acknowledgements\*\//g; # strip comments
64+ $template =~ s/\@ASCIIMathML\.js@/$script/;
65+
66+ return $template;
67+ },
68+ },
69+ # --------------------------------------------------------------------------
70+ 'Text/Pandoc/Writers/DefaultHeaders.hs' => {
71+ # --------------------------------------------------------------------------
72+ proc => sub {
73+ my ($template) = @_;
74+
75+ my (@headers) = split(/\s/,`ls headers`);
76+ foreach my $header (@headers) {
77+ my ($replacement) = escape_for_haskell(slurp "headers/$header");
78+ $template =~ s/\@$header\@/$replacement/;
79+ }
80+
81+ return $template;
82+ },
83+ },
84+ # --------------------------------------------------------------------------
85+ # 'foo/bar/baz' => {
86+ # --------------------------------------------------------------------------
87+ # template => 'optional-template-filename-defaults-to-baz'
88+ # proc => sub {
89+ # my ($template) = @_;
90+ # # Process.
91+ # return $template;
92+ # },
93+ #},
94+);
95+
96+# Main.
97+
98+my $target = shift @ARGV;
99+if (!defined $target || !length $target) {
100+ print STDERR "Available targets:\n\n" . join "\n", keys %processor;
101+ die "\n\nYou must supply a target!\n";
102+}
103+
104+die "No processor exists for '$target'!\n" if ! exists $processor{$target};
105+
106+my $rootdir = shift @ARGV || '..';
107+chdir $rootdir or die "Couldn't chdir to '$rootdir': $!";
108+
109+my $template;
110+if (exists $processor{$target}->{template}) {
111+ $template = $processor{$target}->{template};
112+}
113+else {
114+ ($template = $target) =~ s!.*/+!!;
115+}
116+$template = "templates/$template";
117+die "No template exists for '$target'!\n" if ! -f "$template";
118+
119+open OUTFILE, ">$target" or die "couldn't open file '$target': $!";
120+print OUTFILE <<END;
121+----------------------------------------------------
122+-- Do not edit this file by hand. Edit
123+-- '$template'
124+-- and run $0 $target
125+----------------------------------------------------
126+
127+END
128+
129+print OUTFILE $processor{$target}->{proc}->(slurp($template));
130+print OUTFILE "\n";
131+close OUTFILE;
data/ui/default/blank.gif → src/ui/default/blank.gifrenamed

Binary file not shown.

data/ui/default/bodybg.gif → src/ui/default/bodybg.gifrenamed

Binary file not shown.

data/ui/default/framing.css → src/ui/default/framing.cssrenamed

The content is the same.

data/ui/default/iepngfix.htc → src/ui/default/iepngfix.htcrenamed

The content is the same.

data/ui/default/opera.css → src/ui/default/opera.cssrenamed

The content is the same.

data/ui/default/outline.css → src/ui/default/outline.cssrenamed

The content is the same.

data/ui/default/pretty.css → src/ui/default/pretty.cssrenamed

The content is the same.

data/ui/default/print.css → src/ui/default/print.cssrenamed

The content is the same.

data/ui/default/s5-core.css → src/ui/default/s5-core.cssrenamed

The content is the same.

data/ui/default/slides.css → src/ui/default/slides.cssrenamed

The content is the same.

data/ui/default/slides.js → src/ui/default/slides.jsrenamed

The content is the same.

wrappers/common.sh → src/wrappers/common.shrenamed

The content is the same.

wrappers/hsmarkdown.in → src/wrappers/hsmarkdown.inrenamed

The content is the same.

wrappers/html2markdown.in → src/wrappers/html2markdown.inrenamed

The content is the same.

wrappers/markdown2pdf.in → src/wrappers/markdown2pdf.inrenamed

The content is the same.

wrappers/tempdir.sh → src/wrappers/tempdir.shrenamed

The content is the same.

tests/s5.fancy.html+11−46
@@ -8,38 +8,9 @@
88 /><meta name="author" content="Sam Smith"
99 /><meta name="author" content="Jen Jones"
1010 /><meta name="date" content="July 15, 2006"
11− /><script type="text/javascript"
12− >/*
13−ASCIIMathML.js
14−==============
15−This file contains JavaScript functions to convert ASCII math notation
16−to Presentation MathML. The conversion is done while the (X)HTML page
17−loads, and should work with Firefox/Mozilla/Netscape 7+ and Internet
18−Explorer 6+MathPlayer (http://www.dessci.com/en/products/mathplayer/).
19−Just add the next line to your (X)HTML page with this file in the same folder:
20−<script type="text/javascript" src="ASCIIMathML.js"></script>
21−This is a convenient and inexpensive solution for authoring MathML.
22−
23−Version 1.4.7 Dec 15, 2005, (c) Peter Jipsen http://www.chapman.edu/~jipsen
24−Latest version at http://www.chapman.edu/~jipsen/mathml/ASCIIMathML.js
25−For changes see http://www.chapman.edu/~jipsen/mathml/asciimathchanges.txt
26−If you use it on a webpage, please send the URL to jipsen@chapman.edu
27−
28−Modified July 2006 by John MacFarlane (added CODE to list of contexts
29−in which replacement does not occur, modified AMisMathMLAvailable
30−to better identify Safari browser, changed mathcolor to "").
31−
32−This program is free software; you can redistribute it and/or modify
33−it under the terms of the GNU General Public License as published by
34−the Free Software Foundation; either version 2 of the License, or (at
35−your option) any later version.
36−
37−This program is distributed in the hope that it will be useful,
38−but WITHOUT ANY WARRANTY; without even the implied warranty of
39−MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
40−General Public License (at http://www.gnu.org/copyleft/gpl.html)
41−for more details.
42−*/
11+ /><script type="text/javascript">
12+/* ASCIIMathML.js - copyright Peter Jipsen, released under the GPL
13+See http://www1.chapman.edu/~jipsen/mathml/asciimath.html/ */
4314
4415var checkForMathML = true; // check if browser can display MathML
4516var notifyIfNoMathML = true; // display note if no MathML capability
@@ -88,7 +59,8 @@ function AMnoMathMLNote() {
8859}
8960
9061function AMisMathMLavailable() {
91− var regex = /KHTML/; /* This line and the next two modified by JM for better Safari detection */
62+ var regex = /KHTML/; /* ASCIIMathML.js - copyright Peter Jipsen, released under the GPL
63+See http://www1.chapman.edu/~jipsen/mathml/asciimath.html/ */
9264 if (navigator.appName.slice(0,8)=="Netscape")
9365 if (navigator.appVersion.slice(0,1)>="5" && !regex.test(navigator.userAgent)) return null;
9466 else return AMnoMathMLNote();
@@ -471,16 +443,8 @@ function AMremoveBrackets(node) {
471443 }
472444}
473445
474−/*Parsing ASCII math expressions with the following grammar
475−v ::= [A-Za-z] | greek letters | numbers | other constant symbols
476−u ::= sqrt | text | bb | other unary symbols for font commands
477−b ::= frac | root | stackrel binary symbols
478−l ::= ( | [ | { | (: | {: left brackets
479−r ::= ) | ] | } | :) | :} right brackets
480−S ::= v | lEr | uS | bSS Simple expression
481−I ::= S_S | S^S | S_S^S | S Intermediate expression
482−E ::= IE | I/I Expression
483−Each terminal symbol is translated into a corresponding mathml node.*/
446+/* ASCIIMathML.js - copyright Peter Jipsen, released under the GPL
447+See http://www1.chapman.edu/~jipsen/mathml/asciimath.html/ */
484448
485449var AMnestingDepth,AMpreviousSymbol,AMcurrentSymbol;
486450
@@ -819,7 +783,8 @@ function AMprocessNodeR(n, linebreaks) {
819783 if ((n.nodeType!=8 || linebreaks) &&
820784 n.parentNode.nodeName!="form" && n.parentNode.nodeName!="FORM" &&
821785 n.parentNode.nodeName!="textarea" && n.parentNode.nodeName!="TEXTAREA" &&
822− n.parentNode.nodeName!="code" && n.parentNode.nodeName!="CODE" && /* added by JM */
786+ n.parentNode.nodeName!="code" && n.parentNode.nodeName!="CODE" && /* ASCIIMathML.js - copyright Peter Jipsen, released under the GPL
787+See http://www1.chapman.edu/~jipsen/mathml/asciimath.html/ */
823788 n.parentNode.nodeName!="pre" && n.parentNode.nodeName!="PRE") {
824789 str = n.nodeValue;
825790 if (!(str == null)) {
@@ -954,8 +919,8 @@ else
954919 window.onload = generic;
955920 }
956921}
957−</script
958− ><!-- configuration parameters -->
922+</script>
923+<!-- configuration parameters -->
959924<meta name="defaultView" content="slideshow" />
960925<meta name="controlVis" content="hidden" />
961926<style type="text/css" media="projection" id="slideProj">