git.iko.soy/typeable/pandoc
on master help sign in
Major code cleanup in all modules. (Removed unneeded imports,

reformatted, etc.) More major changes are documented below: + Removed Text.Pandoc.ParserCombinators and moved all its definitions to Text.Pandoc.Shared. + In Text.Pandoc.Shared: - Removed unneeded 'try' in blanklines. - Removed endsWith function and rewrote functions to use isSuffixOf instead. - Added >>~ combinator. - Rewrote stripTrailingNewlines, removeLeadingSpaces. + Moved Text.Pandoc.Entities -> Text.Pandoc.CharacterReferences. - Removed unneeded functions charToEntity, charToNumericalEntity. - Renamed functions using proper terminology (character references, not entities). decodeEntities -> decodeCharacterReferences, characterEntity -> characterReference. - Moved escapeStringToXML to Docbook writer, which is the only thing that uses it. - Removed old entity parser in HTML and Markdown readers; replaced with new charRef parser in Text.Pandoc.Shared. + Fixed accent bug in Text.Pandoc.Readers.LaTeX: \^{} now correctly parses as a '^' character. + Text.Pandoc.ASCIIMathML is no longer an exported module. git-svn-id: https://pandoc.googlecode.com/svn/trunk@835 788f1e2b-df1e-0410-8736-df70ead52e1b

commita8e2199034679c07411c76c42ab1ffb52b170029 authorfiddlosopher <fiddlosopher@788f1e2b-df1e-0410-8736-df70ead52e1b> dateWed 15 Aug 2007, 06:00 (19 years ago) parente814a3f
21 files changed+1994−2263
pandoc.cabal+1−3 src/Text/Pandoc.hs+2−2 src/Text/Pandoc/Blocks.hs+15−11 src/Text/Pandoc/Entities.hs → src/Text/Pandoc/CharacterReferences.hs+20−57 src/Text/Pandoc/Definition.hs+7−8 src/Text/Pandoc/ParserCombinators.hs−198 src/Text/Pandoc/Readers/HTML.hs+160−202 src/Text/Pandoc/Readers/LaTeX.hs+231−305 src/Text/Pandoc/Readers/Markdown.hs+273−389 src/Text/Pandoc/Readers/RST.hs+147−174 src/Text/Pandoc/Shared.hs+576−404 src/Text/Pandoc/Writers/ConTeXt.hs+10−13 src/Text/Pandoc/Writers/Docbook.hs+104−95 src/Text/Pandoc/Writers/HTML.hs+241−234 src/Text/Pandoc/Writers/LaTeX.hs+21−24 src/Text/Pandoc/Writers/Man.hs+10−16 src/Text/Pandoc/Writers/Markdown.hs+35−40 src/Text/Pandoc/Writers/RST.hs+19−27 src/Text/Pandoc/Writers/RTF.hs+46−48 src/templates/DefaultHeaders.hs+29−1 src/templates/S5.hs+47−12
pandoc.cabal+1−3
@@ -34,11 +34,9 @@ Description: Pandoc is a Haskell library for converting from one markup
3434Build-Depends: base, parsec, xhtml, mtl, regex-compat
3535Hs-Source-Dirs: src
3636Exposed-Modules: Text.Pandoc,
37− Text.Pandoc.ASCIIMathML,
3837 Text.Pandoc.Blocks,
3938 Text.Pandoc.Definition,
40− Text.Pandoc.Entities,
41− Text.Pandoc.ParserCombinators,
39+ Text.Pandoc.CharacterReferences,
4240 Text.Pandoc.Shared,
4341 Text.Pandoc.UTF8,
4442 Text.Pandoc.Readers.HTML,
src/Text/Pandoc.hs+2−2
@@ -39,8 +39,8 @@ inline links:
3939>
4040> markdownToRST :: String -> String
4141> markdownToRST = toUTF8 .
42−> (writeRST defaultWriterOptions {writerReferenceLinks = True}) .
43−> (readMarkdown defaultParserState) . fromUTF8
42+> (writeRST defaultWriterOptions {writerReferenceLinks = True}) .
43+> (readMarkdown defaultParserState) . fromUTF8
4444>
4545> main = interact markdownToRST
4646
src/Text/Pandoc/Blocks.hs+15−11
@@ -43,9 +43,8 @@ module Text.Pandoc.Blocks
4343 rightAlignBlock
4444 )
4545where
46−
4746import Text.PrettyPrint
48−import Data.List (transpose, intersperse)
47+import Data.List ( intersperse )
4948
5049-- | A fixed-width block of text. Parameters are width of block,
5150-- height of block, and list of lines.
@@ -53,6 +52,17 @@ data TextBlock = TextBlock Int Int [String]
5352instance Show TextBlock where
5453 show x = show $ blockToDoc x
5554
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+
5666-- | Convert a @Doc@ element into a @TextBlock@ with a specified width.
5767docToBlock :: Int -- ^ Width of text block.
5868 -> Doc -- ^ @Doc@ to convert.
@@ -60,13 +70,8 @@ docToBlock :: Int -- ^ Width of text block.
6070docToBlock width doc =
6171 let rendered = renderStyle (style {lineLength = width,
6272 ribbonsPerLine = 1}) doc
63− lns = lines rendered
64− chop [] = []
65− chop (l:ls) = if length l > width
66− then (take width l):(chop ((drop width l):ls))
67− else l:(chop ls)
68− lns' = chop lns
69− in TextBlock width (length lns') lns'
73+ lns = breakLines width $ lines rendered
74+ in TextBlock width (length lns) lns
7075
7176-- | Convert a @TextBlock@ to a @Doc@ element.
7277blockToDoc :: TextBlock -> Doc
@@ -116,8 +121,7 @@ isWhitespace x = x `elem` " \t"
116121-- | Left-aligns the contents of a @TextBlock@ within the block.
117122leftAlignBlock :: TextBlock -> TextBlock
118123leftAlignBlock (TextBlock width height lns) =
119− TextBlock width height $
120− map (dropWhile isWhitespace) lns
124+ TextBlock width height $ map (dropWhile isWhitespace) lns
121125
122126-- | Right-aligns the contents of a @TextBlock@ within the block.
123127rightAlignBlock :: TextBlock -> TextBlock
src/Text/Pandoc/Entities.hs → src/Text/Pandoc/CharacterReferences.hsrenamed+20−57
@@ -17,7 +17,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1717-}
1818
1919{- |
20− Module : Text.Pandoc.Entities
20+ Module : Text.Pandoc.CharacterReferences
2121 Copyright : Copyright (C) 2006-7 John MacFarlane
2222 License : GNU GPL, version 2 or above
2323
@@ -25,37 +25,26 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2525 Stability : alpha
2626 Portability : portable
2727
28−Functions for encoding unicode characters as entity references,
29−and vice versa.
28+Functions for parsing character references.
3029-}
31−module Text.Pandoc.Entities (
32− charToEntity,
33− charToNumericalEntity,
34− decodeEntities,
35− escapeCharForXML,
36− escapeStringForXML,
37− characterEntity
30+module Text.Pandoc.CharacterReferences (
31+ characterReference,
32+ decodeCharacterReferences,
3833 ) where
39−import Data.Char ( chr, ord )
34+import Data.Char ( chr )
4035import Text.ParserCombinators.Parsec
41−import Data.Maybe ( fromMaybe )
4236import qualified Data.Map as Map
4337
44−-- | Returns a string containing an entity reference for the character.
45−charToEntity :: Char -> String
46−charToEntity char = Map.findWithDefault (charToNumericalEntity char) char reverseEntityTable
47−
48−-- | Returns a string containing a numerical entity reference for the char.
49−charToNumericalEntity :: Char -> String
50−charToNumericalEntity ch = "&#" ++ show (ord ch) ++ ";"
51−
5238-- | Parse character entity.
53−characterEntity :: GenParser Char st Char
54−characterEntity = namedEntity <|> hexEntity <|> decimalEntity <?> "character entity"
39+characterReference :: GenParser Char st Char
40+characterReference = characterEntity <|>
41+ hexadecimalCharacterReference <|>
42+ decimalCharacterReference <?>
43+ "character entity"
5544
5645-- | Parse character entity.
57−namedEntity :: GenParser Char st Char
58−namedEntity = try $ do
46+characterEntity :: GenParser Char st Char
47+characterEntity = try $ do
5948 st <- char '&'
6049 body <- many1 alphaNum
6150 end <- char ';'
@@ -63,8 +52,8 @@ namedEntity = try $ do
6352 return $ Map.findWithDefault '?' entity entityTable
6453
6554-- | Parse hexadecimal entity.
66−hexEntity :: GenParser Char st Char
67−hexEntity = try $ do
55+hexadecimalCharacterReference :: GenParser Char st Char
56+hexadecimalCharacterReference = try $ do
6857 st <- string "&#"
6958 hex <- oneOf "Xx"
7059 body <- many1 (oneOf "0123456789ABCDEFabcdef")
@@ -72,49 +61,23 @@ hexEntity = try $ do
7261 return $ chr $ read ('0':'x':body)
7362
7463-- | Parse decimal entity.
75−decimalEntity :: GenParser Char st Char
76−decimalEntity = try $ do
64+decimalCharacterReference :: GenParser Char st Char
65+decimalCharacterReference = try $ do
7766 st <- string "&#"
7867 body <- many1 digit
7968 end <- char ';'
8069 return $ chr $ read body
8170
82−-- | Escape one character as needed for XML.
83−escapeCharForXML :: Char -> String
84−escapeCharForXML x =
85− case x of
86− '&' -> "&amp;"
87− '<' -> "&lt;"
88− '>' -> "&gt;"
89− '"' -> "&quot;"
90− '\160' -> "&nbsp;"
91− c -> [c]
92−
93−-- | True if the character needs to be escaped.
94−needsEscaping :: Char -> Bool
95−needsEscaping c = c `elem` "&<>\"\160"
96−
97−-- | Escape string as needed for XML. Entity references are not preserved.
98−escapeStringForXML :: String -> String
99−escapeStringForXML "" = ""
100−escapeStringForXML str =
101− case break needsEscaping str of
102− (okay, "") -> okay
103− (okay, (c:cs)) -> okay ++ escapeCharForXML c ++ escapeStringForXML cs
104−
10571-- | Convert entities in a string to characters.
106−decodeEntities :: String -> String
107−decodeEntities str =
108− case parse (many (characterEntity <|> anyChar)) str str of
72+decodeCharacterReferences :: String -> String
73+decodeCharacterReferences str =
74+ case parse (many (characterReference <|> anyChar)) str str of
10975 Left err -> error $ "\nError: " ++ show err
11076 Right result -> result
11177
11278entityTable :: Map.Map String Char
11379entityTable = Map.fromList entityTableList
11480
115−reverseEntityTable :: Map.Map Char String
116−reverseEntityTable = Map.fromList $ map (\(a,b) -> (b,a)) entityTableList
117−
11881entityTableList :: [(String, Char)]
11982entityTableList = [
12083 ("&quot;", chr 34),
src/Text/Pandoc/Definition.hs+7−8
@@ -34,10 +34,10 @@ data Pandoc = Pandoc Meta [Block] deriving (Eq, Read, Show)
3434
3535-- | Bibliographic information for the document: title (list of 'Inline'),
3636-- authors (list of strings), date (string).
37−data Meta = Meta [Inline] -- title
38− [String] -- authors
39− String -- date
40− deriving (Eq, Show, Read)
37+data Meta = Meta [Inline] -- title
38+ [String] -- authors
39+ String -- date
40+ deriving (Eq, Show, Read)
4141
4242-- | Alignment of a table column.
4343data Alignment = AlignLeft
@@ -65,12 +65,11 @@ data ListNumberDelim = DefaultDelim
6565-- | Block element.
6666data Block
6767 = Plain [Inline] -- ^ Plain text, not a paragraph
68− | Null -- ^ Nothing
6968 | Para [Inline] -- ^ Paragraph
7069 | CodeBlock String -- ^ Code block (literal)
7170 | RawHtml String -- ^ Raw HTML block (literal)
7271 | BlockQuote [Block] -- ^ Block quote (list of blocks)
73− | OrderedList ListAttributes [[Block]] -- ^ Ordered list (attributes,
72+ | OrderedList ListAttributes [[Block]] -- ^ Ordered list (attributes
7473 -- and a list of items, each a list of blocks)
7574 | BulletList [[Block]] -- ^ Bullet list (list of items, each
7675 -- a list of blocks)
@@ -84,6 +83,7 @@ data Block
8483 -- relative column widths, column headers
8584 -- (each a list of blocks), and rows
8685 -- (each a list of lists of blocks)
86+ | Null -- ^ Nothing
8787 deriving (Eq, Read, Show)
8888
8989-- | Type of quotation marks to use in Quoted inline.
@@ -112,6 +112,5 @@ data Inline
112112 | Link [Inline] Target -- ^ Hyperlink: text (list of inlines), target
113113 | Image [Inline] Target -- ^ Image: alt text (list of inlines), target
114114 -- and target
115− | Note [Block] -- ^ Footnote or endnote - reference (string),
116− -- text (list of blocks)
115+ | Note [Block] -- ^ Footnote or endnote
117116 deriving (Show, Eq, Read)
src/Text/Pandoc/ParserCombinators.hsdeleted−198
@@ -1,198 +0,0 @@
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.ParserCombinators
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−Parser combinators used in Pandoc readers.
29−-}
30−module Text.Pandoc.ParserCombinators (
31− anyLine,
32− many1Till,
33− notFollowedBy',
34− oneOfStrings,
35− spaceChar,
36− skipSpaces,
37− blankline,
38− blanklines,
39− enclosed,
40− stringAnyCase,
41− parseFromString,
42− lineClump,
43− charsInBalanced,
44− charsInBalanced',
45− romanNumeral,
46− withHorizDisplacement
47− ) where
48−import Text.ParserCombinators.Parsec
49−import Data.Char ( toUpper, toLower )
50−
51−--- | Parse any line of text
52−anyLine :: GenParser Char st [Char]
53−anyLine = try (manyTill anyChar newline) <|> many1 anyChar
54− -- second alternative is for a line ending with eof
55−
56−-- | Parses a space or tab.
57−spaceChar :: CharParser st Char
58−spaceChar = oneOf " \t"
59−
60−-- | Skips zero or more spaces or tabs.
61−skipSpaces :: GenParser Char st ()
62−skipSpaces = skipMany spaceChar
63−
64−-- | Skips zero or more spaces or tabs, then reads a newline.
65−blankline :: GenParser Char st Char
66−blankline = try (do
67− skipSpaces
68− newline)
69−
70−-- | Parses one or more blank lines and returns a string of newlines.
71−blanklines :: GenParser Char st [Char]
72−blanklines = try (many1 blankline)
73−
74−-- | Parses material enclosed between start and end parsers.
75−enclosed :: GenParser Char st t -- ^ start parser
76− -> GenParser Char st end -- ^ end parser
77− -> GenParser Char st a -- ^ content parser (to be used repeatedly)
78− -> GenParser Char st [a]
79−enclosed start end parser = try (do
80− start
81− notFollowedBy space
82− result <- many1Till parser (try end)
83− return result)
84−
85−-- | Like @manyTill@, but reads at least one item.
86−many1Till :: GenParser tok st a
87− -> GenParser tok st end
88− -> GenParser tok st [a]
89−many1Till p end = try (do
90− first <- p
91− rest <- manyTill p end
92− return (first:rest))
93−
94−-- | A more general form of @notFollowedBy@. This one allows any
95−-- type of parser to be specified, and succeeds only if that parser fails.
96−-- It does not consume any input.
97−notFollowedBy' :: Show b => GenParser a st b -> GenParser a st ()
98−notFollowedBy' parser = try (do { c <- try parser; unexpected (show c) }
99− <|> return ())
100−
101−-- | Parses one of a list of strings (tried in order).
102−oneOfStrings :: [String] -> GenParser Char st String
103−oneOfStrings listOfStrings = choice $ map (try . string) listOfStrings
104−
105−-- | Parse string, case insensitive.
106−stringAnyCase :: [Char] -> CharParser st String
107−stringAnyCase [] = string ""
108−stringAnyCase (x:xs) = try (do
109− firstChar <- choice [ char (toUpper x), char (toLower x) ]
110− rest <- stringAnyCase xs
111− return (firstChar:rest))
112−
113−-- | Parse contents of 'str' using 'parser' and return result.
114−parseFromString :: GenParser tok st a -> [tok] -> GenParser tok st a
115−parseFromString parser str = try $ do
116− oldInput <- getInput
117− setInput str
118− result <- parser
119− setInput oldInput
120− return result
121−
122−-- | Parse raw line block up to and including blank lines.
123−lineClump :: GenParser Char st String
124−lineClump = do
125− lines <- many1 (do{notFollowedBy blankline; anyLine})
126− blanks <- blanklines <|> (do{eof; return "\n"})
127− return ((unlines lines) ++ blanks)
128−
129−-- | Parse a string of characters between an open character
130−-- and a close character, including text between balanced
131−-- pairs of open and close. For example,
132−-- @charsInBalanced '(' ')'@ will parse "(hello (there))"
133−-- and return "hello (there)". Stop if a blank line is
134−-- encountered.
135−charsInBalanced :: Char -> Char -> GenParser Char st String
136−charsInBalanced open close = try $ do
137− char open
138− raw <- manyTill ( (do res <- charsInBalanced open close
139− return $ [open] ++ res ++ [close])
140− <|> (do notFollowedBy' (blankline >> blanklines)
141− count 1 anyChar))
142− (char close)
143− return $ concat raw
144−
145−-- | Like charsInBalanced, but allow blank lines in the content.
146−charsInBalanced' :: Char -> Char -> GenParser Char st String
147−charsInBalanced' open close = try $ do
148− char open
149− raw <- manyTill ( (do res <- charsInBalanced open close
150− return $ [open] ++ res ++ [close])
151− <|> count 1 anyChar)
152− (char close)
153− return $ concat raw
154−
155−-- | Parses a roman numeral (uppercase or lowercase), returns number.
156−romanNumeral :: Bool -> -- ^ Uppercase if true
157− GenParser Char st Int
158−romanNumeral upper = try $ do
159− let char' c = char (if upper then toUpper c else c)
160− let one = char' 'i'
161− let five = char' 'v'
162− let ten = char' 'x'
163− let fifty = char' 'l'
164− let hundred = char' 'c'
165− let fivehundred = char' 'd'
166− let thousand = char' 'm'
167− thousands <- many thousand >>= (return . (1000 *) . length)
168− ninehundreds <- option 0 $ try $ hundred >> thousand >> return 900
169− fivehundreds <- many fivehundred >>= (return . (500 *) . length)
170− fourhundreds <- option 0 $ try $ hundred >> fivehundred >> return 400
171− hundreds <- many hundred >>= (return . (100 *) . length)
172− nineties <- option 0 $ try $ ten >> hundred >> return 90
173− fifties <- many fifty >>= (return . (50 *) . length)
174− forties <- option 0 $ try $ ten >> fifty >> return 40
175− tens <- many ten >>= (return . (10 *) . length)
176− nines <- option 0 $ try $ one >> ten >> return 9
177− fives <- many five >>= (return . (5*) . length)
178− fours <- option 0 $ try $ one >> five >> return 4
179− ones <- many one >>= (return . length)
180− let total = thousands + ninehundreds + fivehundreds + fourhundreds +
181− hundreds + nineties + fifties + forties + tens + nines +
182− fives + fours + ones
183− if total == 0
184− then fail "not a roman numeral"
185− else return total
186−
187−-- | Applies a parser, returns tuple of its results and its horizontal
188−-- displacement (the difference between the source column at the end
189−-- and the source column at the beginning). Vertical displacement
190−-- (source row) is ignored.
191−withHorizDisplacement :: GenParser Char st a -- ^ Parser to apply
192− -> GenParser Char st (a, Int) -- ^ (result, displacement)
193−withHorizDisplacement parser = do
194− pos1 <- getPosition
195− result <- parser
196− pos2 <- getPosition
197− return (result, sourceColumn pos2 - sourceColumn pos1)
198−
src/Text/Pandoc/Readers/HTML.hs+160−202
@@ -41,12 +41,12 @@ module Text.Pandoc.Readers.HTML (
4141 ) where
4242
4343import Text.ParserCombinators.Parsec
44−import Text.Pandoc.ParserCombinators
4544import Text.Pandoc.Definition
4645import Text.Pandoc.Shared
47−import Text.Pandoc.Entities ( characterEntity, decodeEntities )
46+import Text.Pandoc.CharacterReferences ( characterReference,
47+ decodeCharacterReferences )
4848import Data.Maybe ( fromMaybe )
49−import Data.List ( intersect, takeWhile, dropWhile )
49+import Data.List ( takeWhile, dropWhile )
5050import Data.Char ( toUpper, toLower, isAlphaNum )
5151
5252-- | Convert HTML-formatted string to 'Pandoc' document.
@@ -55,10 +55,6 @@ readHtml :: ParserState -- ^ Parser state
5555 -> Pandoc
5656readHtml = readWith parseHtml
5757
58−-- for testing
59−testString :: String -> IO ()
60−testString = testStringWith parseHtml
61−
6258--
6359-- Constants
6460--
@@ -74,26 +70,18 @@ inlineHtmlTags = ["a", "abbr", "acronym", "b", "basefont", "bdo", "big",
7470--
7571
7672-- | Read blocks until end tag.
77−blocksTilEnd tag = try (do
78− blocks <- manyTill (do {b <- block; spaces; return b}) (htmlEndTag tag)
79− return $ filter (/= Null) blocks)
73+blocksTilEnd tag = do
74+ blocks <- manyTill (block >>~ spaces) (htmlEndTag tag)
75+ return $ filter (/= Null) blocks
8076
8177-- | Read inlines until end tag.
82−inlinesTilEnd tag = try (do
83− inlines <- manyTill inline (htmlEndTag tag)
84− return inlines)
78+inlinesTilEnd tag = manyTill inline (htmlEndTag tag)
8579
8680-- | Parse blocks between open and close tag.
87−blocksIn tag = try $ do
88− htmlTag tag
89− spaces
90− blocksTilEnd tag
81+blocksIn tag = try $ htmlTag tag >> spaces >> blocksTilEnd tag
9182
9283-- | Parse inlines between open and close tag.
93−inlinesIn tag = try $ do
94− htmlTag tag
95− spaces
96− inlinesTilEnd tag
84+inlinesIn tag = try $ htmlTag tag >> spaces >> inlinesTilEnd tag
9785
9886-- | Extract type from a tag: e.g. @br@ from @\<br\>@
9987extractTagType :: String -> String
@@ -103,19 +91,19 @@ extractTagType ('<':rest) =
10391extractTagType _ = ""
10492
10593-- | Parse any HTML tag (closing or opening) and return text of tag
106−anyHtmlTag = try (do
94+anyHtmlTag = try $ do
10795 char '<'
10896 spaces
10997 tag <- many1 alphaNum
11098 attribs <- htmlAttributes
11199 spaces
112100 ender <- option "" (string "/")
113− let ender' = if (null ender) then "" else " /"
101+ let ender' = if null ender then "" else " /"
114102 spaces
115103 char '>'
116− return ("<" ++ tag ++ attribs ++ ender' ++ ">"))
104+ return $ "<" ++ tag ++ attribs ++ ender' ++ ">"
117105
118−anyHtmlEndTag = try (do
106+anyHtmlEndTag = try $ do
119107 char '<'
120108 spaces
121109 char '/'
@@ -123,19 +111,19 @@ anyHtmlEndTag = try (do
123111 tagType <- many1 alphaNum
124112 spaces
125113 char '>'
126− return ("</" ++ tagType ++ ">"))
114+ return $ "</" ++ tagType ++ ">"
127115
128116htmlTag :: String -> GenParser Char st (String, [(String, String)])
129−htmlTag tag = try (do
117+htmlTag tag = try $ do
130118 char '<'
131119 spaces
132120 stringAnyCase tag
133121 attribs <- many htmlAttribute
134122 spaces
135− option "" (string "/")
123+ optional (string "/")
136124 spaces
137125 char '>'
138− return (tag, (map (\(name, content, raw) -> (name, content)) attribs)))
126+ return (tag, (map (\(name, content, raw) -> (name, content)) attribs))
139127
140128-- parses a quoted html attribute value
141129quoted quoteChar = do
@@ -145,20 +133,20 @@ quoted quoteChar = do
145133
146134htmlAttributes = do
147135 attrList <- many htmlAttribute
148− return (concatMap (\(name, content, raw) -> raw) attrList)
136+ return $ concatMap (\(name, content, raw) -> raw) attrList
149137
150138htmlAttribute = htmlRegularAttribute <|> htmlMinimizedAttribute
151139
152−-- minimized boolean attribute (no = and value)
153−htmlMinimizedAttribute = try (do
140+-- minimized boolean attribute
141+htmlMinimizedAttribute = try $ do
154142 many1 space
155143 name <- many1 (choice [letter, oneOf ".-_:"])
156144 spaces
157145 notFollowedBy (char '=')
158146 let content = name
159− return (name, content, (" " ++ name)))
147+ return (name, content, (" " ++ name))
160148
161−htmlRegularAttribute = try (do
149+htmlRegularAttribute = try $ do
162150 many1 space
163151 name <- many1 (choice [letter, oneOf ".-_:"])
164152 spaces
@@ -170,10 +158,10 @@ htmlRegularAttribute = try (do
170158 a <- many (alphaNum <|> (oneOf "-._:"))
171159 return (a,"")) ]
172160 return (name, content,
173− (" " ++ name ++ "=" ++ quoteStr ++ content ++ quoteStr)))
161+ (" " ++ name ++ "=" ++ quoteStr ++ content ++ quoteStr))
174162
175163-- | Parse an end tag of type 'tag'
176−htmlEndTag tag = try (do
164+htmlEndTag tag = try $ do
177165 char '<'
178166 spaces
179167 char '/'
@@ -181,87 +169,83 @@ htmlEndTag tag = try (do
181169 stringAnyCase tag
182170 spaces
183171 char '>'
184− return ("</" ++ tag ++ ">"))
172+ return $ "</" ++ tag ++ ">"
185173
186174-- | Returns @True@ if the tag is an inline tag.
187175isInline tag = (extractTagType tag) `elem` inlineHtmlTags
188176
189−anyHtmlBlockTag = try (do
190− tag <- choice [anyHtmlTag, anyHtmlEndTag]
191− if isInline tag then fail "inline tag" else return tag)
177+anyHtmlBlockTag = try $ do
178+ tag <- anyHtmlTag <|> anyHtmlEndTag
179+ if isInline tag then fail "inline tag" else return tag
192180
193−anyHtmlInlineTag = try (do
194− tag <- choice [ anyHtmlTag, anyHtmlEndTag ]
195− if isInline tag then return tag else fail "not an inline tag")
181+anyHtmlInlineTag = try $ do
182+ tag <- anyHtmlTag <|> anyHtmlEndTag
183+ if isInline tag then return tag else fail "not an inline tag"
196184
197185-- | Parses material between script tags.
198186-- Scripts must be treated differently, because they can contain '<>' etc.
199−htmlScript = try (do
187+htmlScript = try $ do
200188 open <- string "<script"
201189 rest <- manyTill anyChar (htmlEndTag "script")
202− return (open ++ rest ++ "</script>"))
190+ return $ open ++ rest ++ "</script>"
203191
204192htmlBlockElement = choice [ htmlScript, htmlComment, xmlDec, definition ]
205193
206−rawHtmlBlock = try (do
207− notFollowedBy' (choice [htmlTag "/body", htmlTag "/html"])
194+rawHtmlBlock = try $ do
195+ notFollowedBy' (htmlTag "/body" <|> htmlTag "/html")
208196 body <- htmlBlockElement <|> anyHtmlBlockTag
209− sp <- (many space)
197+ sp <- many space
210198 state <- getState
211− if stateParseRaw state then return (RawHtml (body ++ sp)) else return Null)
199+ if stateParseRaw state then return (RawHtml (body ++ sp)) else return Null
212200
213201-- | Parses an HTML comment.
214−htmlComment = try (do
202+htmlComment = try $ do
215203 string "<!--"
216204 comment <- manyTill anyChar (try (string "-->"))
217− return ("<!--" ++ comment ++ "-->"))
205+ return $ "<!--" ++ comment ++ "-->"
218206
219207--
220208-- parsing documents
221209--
222210
223−xmlDec = try (do
211+xmlDec = try $ do
224212 string "<?"
225213 rest <- manyTill anyChar (char '>')
226− return ("<?" ++ rest ++ ">"))
214+ return $ "<?" ++ rest ++ ">"
227215
228−definition = try (do
216+definition = try $ do
229217 string "<!"
230218 rest <- manyTill anyChar (char '>')
231− return ("<!" ++ rest ++ ">"))
219+ return $ "<!" ++ rest ++ ">"
232220
233−nonTitleNonHead = try (do
234− notFollowedBy' (htmlTag "title")
235− notFollowedBy' (htmlTag "/head")
236− result <- choice [do {rawHtmlBlock; return ' '}, anyChar]
237− return result)
221+nonTitleNonHead = try $ notFollowedBy' (htmlTag "title" <|> htmlTag "/head") >>
222+ ((rawHtmlBlock >> return ' ') <|> anyChar)
238223
239−parseTitle = try (do
240− (tag, attribs) <- htmlTag "title"
224+parseTitle = try $ do
225+ (tag, _) <- htmlTag "title"
241226 contents <- inlinesTilEnd tag
242227 spaces
243− return contents)
228+ return contents
244229
245230-- parse header and return meta-information (for now, just title)
246−parseHead = try (do
231+parseHead = try $ do
247232 htmlTag "head"
248233 spaces
249234 skipMany nonTitleNonHead
250235 contents <- option [] parseTitle
251236 skipMany nonTitleNonHead
252237 htmlTag "/head"
253− return (contents, [], ""))
238+ return (contents, [], "")
254239
255−skipHtmlTag tag = option ("",[]) (htmlTag tag)
240+skipHtmlTag tag = optional (htmlTag tag)
256241
257242-- h1 class="title" representation of title in body
258−bodyTitle = try (do
243+bodyTitle = try $ do
259244 (tag, attribs) <- htmlTag "h1"
260245 cl <- case (extractAttribute "class" attribs) of
261− Just "title" -> do {return ""}
246+ Just "title" -> return ""
262247 otherwise -> fail "not title"
263248 inlinesTilEnd "h1"
264− return "")
265249
266250parseHtml = do
267251 sepEndBy (choice [xmlDec, definition, htmlComment]) spaces
@@ -271,27 +255,30 @@ parseHtml = do
271255 spaces
272256 skipHtmlTag "body"
273257 spaces
274− option "" bodyTitle -- skip title in body, because it's represented in meta
258+ optional bodyTitle -- skip title in body, because it's represented in meta
275259 blocks <- parseBlocks
276260 spaces
277− option "" (htmlEndTag "body")
261+ optional (htmlEndTag "body")
278262 spaces
279− option "" (htmlEndTag "html")
263+ optional (htmlEndTag "html")
280264 many anyChar -- ignore anything after </html>
281265 eof
282− return (Pandoc (Meta title authors date) blocks)
266+ return $ Pandoc (Meta title authors date) blocks
283267
284268--
285269-- parsing blocks
286270--
287271
288−parseBlocks = do
289− spaces
290− result <- sepEndBy block spaces
291− return $ filter (/= Null) result
272+parseBlocks = spaces >> sepEndBy block spaces >>= (return . filter (/= Null))
292273
293−block = choice [ codeBlock, header, hrule, list, blockQuote, para, plain,
294− rawHtmlBlock ] <?> "block"
274+block = choice [ codeBlock
275+ , header
276+ , hrule
277+ , list
278+ , blockQuote
279+ , para
280+ , plain
281+ , rawHtmlBlock ] <?> "block"
295282
296283--
297284-- header blocks
@@ -299,53 +286,49 @@ block = choice [ codeBlock, header, hrule, list, blockQuote, para, plain,
299286
300287header = choice (map headerLevel (enumFromTo 1 5)) <?> "header"
301288
302−headerLevel n = try (do
289+headerLevel n = try $ do
303290 let level = "h" ++ show n
304291 (tag, attribs) <- htmlTag level
305292 contents <- inlinesTilEnd level
306− return (Header n (normalizeSpaces contents)))
293+ return $ Header n (normalizeSpaces contents)
307294
308295--
309296-- hrule block
310297--
311298
312−hrule = try (do
299+hrule = try $ do
313300 (tag, attribs) <- htmlTag "hr"
314301 state <- getState
315− if (not (null attribs)) && (stateParseRaw state)
316− then -- in this case we want to parse it as raw html
317− unexpected "attributes in hr"
318− else return HorizontalRule)
302+ if not (null attribs) && stateParseRaw state
303+ then unexpected "attributes in hr" -- parse as raw in this case
304+ else return HorizontalRule
319305
320306--
321307-- code blocks
322308--
323309
324−codeBlock = choice [ preCodeBlock, bareCodeBlock ] <?> "code block"
310+codeBlock = preCodeBlock <|> bareCodeBlock <?> "code block"
325311
326−preCodeBlock = try (do
312+preCodeBlock = try $ do
327313 htmlTag "pre"
328314 spaces
329− htmlTag "code"
330− result <- manyTill anyChar (htmlEndTag "code")
315+ result <- bareCodeBlock
331316 spaces
332317 htmlEndTag "pre"
333− return (CodeBlock (stripTrailingNewlines (decodeEntities result))))
318+ return result
334319
335−bareCodeBlock = try (do
320+bareCodeBlock = try $ do
336321 htmlTag "code"
337322 result <- manyTill anyChar (htmlEndTag "code")
338− return (CodeBlock (stripTrailingNewlines (decodeEntities result))))
323+ return $ CodeBlock $ stripTrailingNewlines $
324+ decodeCharacterReferences result
339325
340326--
341327-- block quotes
342328--
343329
344−blockQuote = try (do
345− tag <- htmlTag "blockquote"
346− spaces
347− blocks <- blocksTilEnd "blockquote"
348− return (BlockQuote blocks))
330+blockQuote = try $ htmlTag "blockquote" >> spaces >>
331+ blocksTilEnd "blockquote" >>= (return . BlockQuote)
349332
350333--
351334-- list blocks
@@ -354,119 +337,105 @@ blockQuote = try (do
354337list = choice [ bulletList, orderedList, definitionList ] <?> "list"
355338
356339orderedList = try $ do
357− (_, attribs) <- htmlTag "ol"
358− (start, style) <- option (1, DefaultStyle) $
359− do failIfStrict
360− let sta = fromMaybe "1" $
361− lookup "start" attribs
362− let sty = fromMaybe (fromMaybe "" $
363− lookup "style" attribs) $
364− lookup "class" attribs
365− let sty' = case sty of
366− "lower-roman" -> LowerRoman
367− "upper-roman" -> UpperRoman
368− "lower-alpha" -> LowerAlpha
369− "upper-alpha" -> UpperAlpha
370− "decimal" -> Decimal
371− _ -> DefaultStyle
372− return (read sta, sty')
373− spaces
374− items <- sepEndBy1 (blocksIn "li") spaces
375− htmlEndTag "ol"
376− return (OrderedList (start, style, DefaultDelim) items)
340+ (_, attribs) <- htmlTag "ol"
341+ (start, style) <- option (1, DefaultStyle) $
342+ do failIfStrict
343+ let sta = fromMaybe "1" $
344+ lookup "start" attribs
345+ let sty = fromMaybe (fromMaybe "" $
346+ lookup "style" attribs) $
347+ lookup "class" attribs
348+ let sty' = case sty of
349+ "lower-roman" -> LowerRoman
350+ "upper-roman" -> UpperRoman
351+ "lower-alpha" -> LowerAlpha
352+ "upper-alpha" -> UpperAlpha
353+ "decimal" -> Decimal
354+ _ -> DefaultStyle
355+ return (read sta, sty')
356+ spaces
357+ items <- sepEndBy1 (blocksIn "li") spaces
358+ htmlEndTag "ol"
359+ return $ OrderedList (start, style, DefaultDelim) items
377360
378361bulletList = try $ do
379− htmlTag "ul"
380− spaces
381− items <- sepEndBy1 (blocksIn "li") spaces
382− htmlEndTag "ul"
383− return (BulletList items)
362+ htmlTag "ul"
363+ spaces
364+ items <- sepEndBy1 (blocksIn "li") spaces
365+ htmlEndTag "ul"
366+ return $ BulletList items
384367
385368definitionList = try $ do
386− failIfStrict -- def lists not part of standard markdown
387− tag <- htmlTag "dl"
388− spaces
389− items <- sepEndBy1 definitionListItem spaces
390− htmlEndTag "dl"
391− return (DefinitionList items)
369+ failIfStrict -- def lists not part of standard markdown
370+ tag <- htmlTag "dl"
371+ spaces
372+ items <- sepEndBy1 definitionListItem spaces
373+ htmlEndTag "dl"
374+ return $ DefinitionList items
392375
393376definitionListItem = try $ do
394− terms <- sepEndBy1 (inlinesIn "dt") spaces
395− defs <- sepEndBy1 (blocksIn "dd") spaces
396− let term = joinWithSep [LineBreak] terms
397− return (term, concat defs)
377+ terms <- sepEndBy1 (inlinesIn "dt") spaces
378+ defs <- sepEndBy1 (blocksIn "dd") spaces
379+ let term = joinWithSep [LineBreak] terms
380+ return (term, concat defs)
398381
399382--
400383-- paragraph block
401384--
402385
403−para = try (do
404− tag <- htmlTag "p"
405− result <- inlinesTilEnd "p"
406− return (Para (normalizeSpaces result)))
386+para = htmlTag "p" >> inlinesTilEnd "p" >>= return . Para . normalizeSpaces
407387
408388--
409389-- plain block
410390--
411391
412−plain = do
413− result <- many1 inline
414− return (Plain (normalizeSpaces result))
392+plain = many1 inline >>= return . Plain . normalizeSpaces
415393
416394--
417395-- inline
418396--
419397
420−inline = choice [ text, special ] <?> "inline"
421−
422−text = choice [ entity, strong, emph, superscript, subscript,
423− strikeout, spanStrikeout, code, str,
424− linebreak, whitespace ] <?> "text"
425−
426−special = choice [ link, image, rawHtmlInline ] <?>
427− "link, inline html, or image"
428−
429−entity = do
430− ent <- characterEntity
431− return $ Str [ent]
432−
433−code = try (do
398+inline = choice [ charRef
399+ , strong
400+ , emph
401+ , superscript
402+ , subscript
403+ , strikeout
404+ , spanStrikeout
405+ , code
406+ , str
407+ , linebreak
408+ , whitespace
409+ , link
410+ , image
411+ , rawHtmlInline ] <?> "inline"
412+
413+code = try $ do
434414 htmlTag "code"
435415 result <- manyTill anyChar (htmlEndTag "code")
436416 -- remove internal line breaks, leading and trailing space,
437− -- and decode entities
438− let result' = decodeEntities $ removeLeadingTrailingSpace $
439− joinWithSep " " $ lines result
440− return (Code result'))
417+ -- and decode character references
418+ return $ Code $ decodeCharacterReferences $ removeLeadingTrailingSpace $
419+ joinWithSep " " $ lines result
441420
442421rawHtmlInline = do
443− result <- choice [htmlScript, anyHtmlInlineTag]
422+ result <- htmlScript <|> anyHtmlInlineTag
444423 state <- getState
445424 if stateParseRaw state then return (HtmlInline result) else return (Str "")
446425
447−betweenTags tag = try (do
448− htmlTag tag
449− result <- inlinesTilEnd tag
450− return (normalizeSpaces result))
426+betweenTags tag = try $ htmlTag tag >> inlinesTilEnd tag >>=
427+ return . normalizeSpaces
451428
452−emph = try (do
453− result <- choice [betweenTags "em", betweenTags "it"]
454− return (Emph result))
429+emph = (betweenTags "em" <|> betweenTags "it") >>= return . Emph
455430
456−superscript = try $ do
457− failIfStrict -- strict markdown has no superscript, so treat as raw HTML
458− result <- betweenTags "sup"
459− return (Superscript result)
431+strong = (betweenTags "b" <|> betweenTags "strong") >>= return . Strong
460432
461−subscript = try $ do
462− failIfStrict -- strict markdown has no subscript, so treat as raw HTML
463− result <- betweenTags "sub"
464− return (Subscript result)
433+superscript = failIfStrict >> betweenTags "sup" >>= return . Superscript
465434
466−strikeout = try $ do
467− failIfStrict -- strict markdown has no strikeout, so treat as raw HTML
468− result <- choice [betweenTags "s", betweenTags "strike"]
469− return (Strikeout result)
435+subscript = failIfStrict >> betweenTags "sub" >>= return . Subscript
436+
437+strikeout = failIfStrict >> (betweenTags "s" <|> betweenTags "strike") >>=
438+ return . Strikeout
470439
471440spanStrikeout = try $ do
472441 failIfStrict -- strict markdown has no strikeout, so treat as raw HTML
@@ -474,25 +443,14 @@ spanStrikeout = try $ do
474443 result <- case (extractAttribute "class" attributes) of
475444 Just "strikeout" -> inlinesTilEnd "span"
476445 _ -> fail "not a strikeout"
477− return (Strikeout result)
446+ return $ Strikeout result
478447
479−strong = try (do
480− result <- choice [betweenTags "b", betweenTags "strong"]
481− return (Strong result))
482−
483−whitespace = do
484− many1 space
485− return Space
448+whitespace = many1 space >> return Space
486449
487450-- hard line break
488−linebreak = do
489− htmlTag "br"
490− option ' ' newline
491− return LineBreak
451+linebreak = htmlTag "br" >> optional newline >> return LineBreak
492452
493−str = do
494− result <- many1 (noneOf "<& \t\n")
495− return (Str result)
453+str = many1 (noneOf "<& \t\n") >>= return . Str
496454
497455--
498456-- links and images
@@ -501,27 +459,27 @@ str = do
501459-- extract contents of attribute (attribute names are case-insensitive)
502460extractAttribute name [] = Nothing
503461extractAttribute name ((attrName, contents):rest) =
504− let name' = map toLower name
505− attrName' = map toLower attrName in
506− if (attrName' == name')
507− then Just (decodeEntities contents)
508− else extractAttribute name rest
462+ let name' = map toLower name
463+ attrName' = map toLower attrName
464+ in if attrName' == name'
465+ then Just (decodeCharacterReferences contents)
466+ else extractAttribute name rest
509467
510468link = try $ do
511469 (tag, attributes) <- htmlTag "a"
512470 url <- case (extractAttribute "href" attributes) of
513− Just url -> do {return url}
471+ Just url -> return url
514472 Nothing -> fail "no href"
515− let title = fromMaybe "" (extractAttribute "title" attributes)
473+ let title = fromMaybe "" $ extractAttribute "title" attributes
516474 label <- inlinesTilEnd "a"
517475 return $ Link (normalizeSpaces label) (url, title)
518476
519477image = try $ do
520478 (tag, attributes) <- htmlTag "img"
521479 url <- case (extractAttribute "src" attributes) of
522− Just url -> do {return url}
480+ Just url -> return url
523481 Nothing -> fail "no src"
524− let title = fromMaybe "" (extractAttribute "title" attributes)
482+ let title = fromMaybe "" $ extractAttribute "title" attributes
525483 let alt = fromMaybe "" (extractAttribute "alt" attributes)
526484 return $ Image [Str alt] (url, title)
527485
src/Text/Pandoc/Readers/LaTeX.hs+231−305
@@ -34,7 +34,6 @@ module Text.Pandoc.Readers.LaTeX (
3434 ) where
3535
3636import Text.ParserCombinators.Parsec
37−import Text.Pandoc.ParserCombinators
3837import Text.Pandoc.Definition
3938import Text.Pandoc.Shared
4039import Data.Maybe ( fromMaybe )
@@ -47,9 +46,6 @@ readLaTeX :: ParserState -- ^ Parser state, including options for parser
4746 -> Pandoc
4847readLaTeX = readWith parseLaTeX
4948
50−-- for testing
51−testString = testStringWith parseLaTeX
52−
5349-- characters with special meaning
5450specialChars = "\\$%&^&_~#{}\n \t|<>'\"-"
5551
@@ -58,12 +54,12 @@ specialChars = "\\$%&^&_~#{}\n \t|<>'\"-"
5854--
5955
6056-- | Returns text between brackets and its matching pair.
61−bracketedText openB closeB = try (do
57+bracketedText openB closeB = do
6258 result <- charsInBalanced' openB closeB
63− return ([openB] ++ result ++ [closeB]))
59+ return $ [openB] ++ result ++ [closeB]
6460
6561-- | Returns an option or argument of a LaTeX command.
66−optOrArg = choice [ (bracketedText '{' '}'), (bracketedText '[' ']') ]
62+optOrArg = bracketedText '{' '}' <|> bracketedText '[' ']'
6763
6864-- | True if the string begins with '{'.
6965isArg ('{':rest) = True
@@ -73,62 +69,55 @@ isArg other = False
7369commandArgs = many optOrArg
7470
7571-- | Parses LaTeX command, returns (name, star, list of options or arguments).
76−command = try (do
72+command = try $ do
7773 char '\\'
7874 name <- many1 alphaNum
7975 star <- option "" (string "*") -- some commands have starred versions
8076 args <- commandArgs
81− return (name, star, args))
77+ return (name, star, args)
8278
83−begin name = try (do
84− string "\\begin{"
85− string name
86− char '}'
87− option [] commandArgs
79+begin name = try $ do
80+ string $ "\\begin{" ++ name ++ "}"
81+ optional commandArgs
8882 spaces
89− return name)
83+ return name
9084
91−end name = try (do
92− string "\\end{"
93− string name
94− char '}'
85+end name = try $ do
86+ string $ "\\end{" ++ name ++ "}"
9587 spaces
96− return name)
88+ return name
9789
9890-- | Returns a list of block elements containing the contents of an
9991-- environment.
100−environment name = try (do
101− begin name
102− spaces
103− contents <- manyTill block (end name)
104− return contents)
92+environment name = try $ begin name >> spaces >> manyTill block (end name)
10593
106−anyEnvironment = try (do
94+anyEnvironment = try $ do
10795 string "\\begin{"
10896 name <- many alphaNum
10997 star <- option "" (string "*") -- some environments have starred variants
11098 char '}'
111− option [] commandArgs
99+ optional commandArgs
112100 spaces
113101 contents <- manyTill block (end (name ++ star))
114− return (BlockQuote contents))
102+ return $ BlockQuote contents
115103
116104--
117105-- parsing documents
118106--
119107
120108-- | Process LaTeX preamble, extracting metadata.
121−processLaTeXPreamble = try (do
122− manyTill (choice [bibliographic, comment, unknownCommand, nullBlock])
123− (try (string "\\begin{document}"))
124− spaces)
109+processLaTeXPreamble = try $ manyTill
110+ (choice [bibliographic, comment, unknownCommand, nullBlock])
111+ (try (string "\\begin{document}")) >>
112+ spaces
125113
126114-- | Parse LaTeX and return 'Pandoc'.
127115parseLaTeX = do
128− option () processLaTeXPreamble -- preamble might not be present (fragment)
116+ optional processLaTeXPreamble -- preamble might not be present (fragment)
117+ spaces
129118 blocks <- parseBlocks
130119 spaces
131− option "" (try (string "\\end{document}")) -- might not be present (in fragment)
120+ optional $ try (string "\\end{document}") -- might not be present (fragment)
132121 spaces
133122 eof
134123 state <- getState
@@ -136,21 +125,27 @@ parseLaTeX = do
136125 let title' = stateTitle state
137126 let authors' = stateAuthors state
138127 let date' = stateDate state
139− return (Pandoc (Meta title' authors' date') blocks')
128+ return $ Pandoc (Meta title' authors' date') blocks'
140129
141130--
142131-- parsing blocks
143132--
144133
145−parseBlocks = do
146− spaces
147− result <- many block
148− return result
149−
150−block = choice [ hrule, codeBlock, header, list, blockQuote, mathBlock,
151− comment, bibliographic, para, specialEnvironment,
152− itemBlock, unknownEnvironment, unknownCommand ] <?>
153− "block"
134+parseBlocks = spaces >> many block
135+
136+block = choice [ hrule
137+ , codeBlock
138+ , header
139+ , list
140+ , blockQuote
141+ , mathBlock
142+ , comment
143+ , bibliographic
144+ , para
145+ , specialEnvironment
146+ , itemBlock
147+ , unknownEnvironment
148+ , unknownCommand ] <?> "block"
154149
155150--
156151-- header blocks
@@ -158,24 +153,21 @@ block = choice [ hrule, codeBlock, header, list, blockQuote, mathBlock,
158153
159154header = choice (map headerLevel (enumFromTo 1 5)) <?> "header"
160155
161−headerLevel n = try (do
156+headerLevel n = try $ do
162157 let subs = concat $ replicate (n - 1) "sub"
163158 string ("\\" ++ subs ++ "section")
164− option ' ' (char '*')
159+ optional (char '*')
165160 char '{'
166161 title <- manyTill inline (char '}')
167162 spaces
168− return (Header n (normalizeSpaces title)))
163+ return $ Header n (normalizeSpaces title)
169164
170165--
171166-- hrule block
172167--
173168
174−hrule = try (do
175− oneOfStrings [ "\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n\n",
176− "\\newpage" ]
177− spaces
178− return HorizontalRule)
169+hrule = oneOfStrings [ "\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n\n",
170+ "\\newpage" ] >> spaces >> return HorizontalRule
179171
180172--
181173-- code blocks
@@ -183,37 +175,28 @@ hrule = try (do
183175
184176codeBlock = codeBlock1 <|> codeBlock2
185177
186−codeBlock1 = try (do
178+codeBlock1 = try $ do
187179 string "\\begin{verbatim}" -- don't use begin function because it
188180 -- gobbles whitespace
189− option "" blanklines -- we want to gobble blank lines, but not
181+ optional blanklines -- we want to gobble blank lines, but not
190182 -- leading space
191183 contents <- manyTill anyChar (try (string "\\end{verbatim}"))
192184 spaces
193− return (CodeBlock (stripTrailingNewlines contents)))
185+ return $ CodeBlock (stripTrailingNewlines contents)
194186
195−codeBlock2 = try (do
196− string "\\begin{Verbatim}" -- used by fancyverb package
187+codeBlock2 = try $ do
188+ string "\\begin{Verbatim}" -- used by fancyvrb package
197189 option "" blanklines
198190 contents <- manyTill anyChar (try (string "\\end{Verbatim}"))
199191 spaces
200− return (CodeBlock (stripTrailingNewlines contents)))
192+ return $ CodeBlock (stripTrailingNewlines contents)
201193
202194--
203195-- block quotes
204196--
205197
206−blockQuote = choice [ blockQuote1, blockQuote2 ] <?> "blockquote"
207−
208−blockQuote1 = try (do
209− blocks <- environment "quote"
210− spaces
211− return (BlockQuote blocks))
212−
213−blockQuote2 = try (do
214− blocks <- environment "quotation"
215− spaces
216− return (BlockQuote blocks))
198+blockQuote = (environment "quote" <|> environment "quotation") >>~ spaces >>=
199+ return . BlockQuote
217200
218201--
219202-- math block
@@ -223,12 +206,12 @@ mathBlock = mathBlockWith (begin "equation") (end "equation") <|>
223206 mathBlockWith (begin "displaymath") (end "displaymath") <|>
224207 mathBlockWith (string "\\[") (string "\\]") <?> "math block"
225208
226−mathBlockWith start end = try (do
209+mathBlockWith start end = try $ do
227210 start
228211 spaces
229212 result <- manyTill anyChar end
230213 spaces
231− return (BlockQuote [Para [TeX ("$" ++ result ++ "$")]]))
214+ return $ BlockQuote [Para [TeX ("$" ++ result ++ "$")]]
232215
233216--
234217-- list blocks
@@ -237,69 +220,66 @@ mathBlockWith start end = try (do
237220list = bulletList <|> orderedList <|> definitionList <?> "list"
238221
239222listItem = try $ do
240− ("item", _, args) <- command
241− spaces
242− state <- getState
243− let oldParserContext = stateParserContext state
244− updateState (\state -> state {stateParserContext = ListItemState})
245− blocks <- many block
246− updateState (\state -> state {stateParserContext = oldParserContext})
247− opt <- case args of
248− ([x]) | "[" `isPrefixOf` x && "]" `isSuffixOf` x ->
249− parseFromString (many inline) $ tail $ init x
250− _ -> return []
251− return (opt, blocks)
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)
252235
253236orderedList = try $ do
254− string "\\begin{enumerate}"
255− (_, style, delim) <- option (1, DefaultStyle, DefaultDelim) $
256− try $ do failIfStrict
257− char '['
258− res <- anyOrderedListMarker
259− char ']'
260− return res
261− spaces
262− option "" $ try $ do string "\\setlength{\\itemindent}"
263− char '{'
264− manyTill anyChar (char '}')
265− spaces
266− start <- option 1 $ try $ do failIfStrict
267− string "\\setcounter{enum"
268− many1 (char 'i')
269− string "}{"
270− num <- many1 digit
271− char '}'
272− spaces
273− return $ (read num) + 1
274− items <- many listItem
275− end "enumerate"
276− spaces
277− return $ OrderedList (start, style, delim) $ map snd items
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 (char 'i')
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
278261
279262bulletList = try $ do
280− begin "itemize"
281− spaces
282− items <- many listItem
283− end "itemize"
284− spaces
285− return (BulletList $ map snd items)
263+ begin "itemize"
264+ spaces
265+ items <- many listItem
266+ end "itemize"
267+ spaces
268+ return (BulletList $ map snd items)
286269
287270definitionList = try $ do
288− begin "description"
289− spaces
290− items <- many listItem
291− end "description"
292− spaces
293− return (DefinitionList items)
271+ begin "description"
272+ spaces
273+ items <- many listItem
274+ end "description"
275+ spaces
276+ return (DefinitionList items)
294277
295278--
296279-- paragraph block
297280--
298281
299−para = try (do
300− result <- many1 inline
301− spaces
302− return (Para (normalizeSpaces result)))
282+para = many1 inline >>~ spaces >>= return . Para . normalizeSpaces
303283
304284--
305285-- title authors date
@@ -307,33 +287,30 @@ para = try (do
307287
308288bibliographic = choice [ maketitle, title, authors, date ]
309289
310−maketitle = try (do
311− string "\\maketitle"
312− spaces
313− return Null)
290+maketitle = try (string "\\maketitle") >> spaces >> return Null
314291
315−title = try (do
292+title = try $ do
316293 string "\\title{"
317294 tit <- manyTill inline (char '}')
318295 spaces
319296 updateState (\state -> state { stateTitle = tit })
320− return Null)
297+ return Null
321298
322−authors = try (do
299+authors = try $ do
323300 string "\\author{"
324301 authors <- manyTill anyChar (char '}')
325302 spaces
326303 let authors' = map removeLeadingTrailingSpace $ lines $
327304 substitute "\\\\" "\n" authors
328305 updateState (\state -> state { stateAuthors = authors' })
329− return Null)
306+ return Null
330307
331−date = try (do
308+date = try $ do
332309 string "\\date{"
333310 date' <- manyTill anyChar (char '}')
334311 spaces
335312 updateState (\state -> state { stateDate = date' })
336− return Null)
313+ return Null
337314
338315--
339316-- item block
@@ -341,14 +318,14 @@ date = try (do
341318--
342319
343320-- this forces items to be parsed in different blocks
344−itemBlock = try (do
321+itemBlock = try $ do
345322 ("item", _, args) <- command
346323 state <- getState
347324 if (stateParserContext state == ListItemState)
348325 then fail "item should be handled by list block"
349326 else if null args
350327 then return Null
351− else return (Plain [Str (stripFirstAndLast (head args))]))
328+ else return $ Plain [Str (stripFirstAndLast (head args))]
352329
353330--
354331-- raw LaTeX
@@ -362,77 +339,93 @@ specialEnvironment = do -- these are always parsed as raw
362339-- | Parse any LaTeX environment and return a Para block containing
363340-- the whole literal environment as raw TeX.
364341rawLaTeXEnvironment :: GenParser Char st Block
365−rawLaTeXEnvironment = try (do
366− string "\\begin"
367− char '{'
342+rawLaTeXEnvironment = try $ do
343+ string "\\begin{"
368344 name <- many1 alphaNum
369345 star <- option "" (string "*") -- for starred variants
370346 let name' = name ++ star
371347 char '}'
372348 args <- option [] commandArgs
373349 let argStr = concat args
374− contents <- manyTill (choice [(many1 (noneOf "\\")),
350+ contents <- manyTill (choice [ (many1 (noneOf "\\")),
375351 (do
376352 (Para [TeX str]) <- rawLaTeXEnvironment
377353 return str),
378354 string "\\" ])
379355 (end name')
380356 spaces
381− return (Para [TeX ("\\begin{" ++ name' ++ "}" ++ argStr ++
382− (concat contents) ++ "\\end{" ++ name' ++ "}")]))
357+ return $ Para [TeX $ "\\begin{" ++ name' ++ "}" ++ argStr ++
358+ concat contents ++ "\\end{" ++ name' ++ "}"]
383359
384−unknownEnvironment = try (do
360+unknownEnvironment = try $ do
385361 state <- getState
386362 result <- if stateParseRaw state -- check whether we should include raw TeX
387363 then rawLaTeXEnvironment -- if so, get whole raw environment
388364 else anyEnvironment -- otherwise just the contents
389− return result)
365+ return result
390366
391−unknownCommand = try (do
392− notFollowedBy' $ choice $ map end
393− ["itemize", "enumerate", "description", "document"]
367+unknownCommand = try $ do
368+ notFollowedBy' $ choice $ map end ["itemize", "enumerate", "description",
369+ "document"]
394370 (name, star, args) <- command
395371 spaces
396372 let argStr = concat args
397373 state <- getState
398− if (name == "item") && ((stateParserContext state) == ListItemState)
374+ if name == "item" && (stateParserContext state) == ListItemState
399375 then fail "should not be parsed as raw"
400376 else string ""
401377 if stateParseRaw state
402− then return (Plain [TeX ("\\" ++ name ++ star ++ argStr)])
403− else return (Plain [Str (joinWithSep " " args)]))
378+ then return $ Plain [TeX ("\\" ++ name ++ star ++ argStr)]
379+ else return $ Plain [Str (joinWithSep " " args)]
404380
405381-- latex comment
406−comment = try (do
407− char '%'
408− result <- manyTill anyChar newline
409− spaces
410− return Null)
382+comment = try $ char '%' >> manyTill anyChar newline >> spaces >> return Null
411383
412384--
413385-- inline
414386--
415387
416−inline = choice [ strong, emph, strikeout, superscript, subscript,
417− ref, lab, code, linebreak, spacer,
418− math, ellipses, emDash, enDash, hyphen, quoted, apostrophe,
419− accentedChar, specialChar, specialInline, escapedChar,
420− unescapedChar, str, endline, whitespace ] <?> "inline"
421−
422−specialInline = choice [ url, link, image, footnote, rawLaTeXInline ]
423− <?> "link, raw TeX, note, or image"
388+inline = choice [ strong
389+ , emph
390+ , strikeout
391+ , superscript
392+ , subscript
393+ , ref
394+ , lab
395+ , code
396+ , linebreak
397+ , spacer
398+ , math
399+ , ellipses
400+ , emDash
401+ , enDash
402+ , hyphen
403+ , quoted
404+ , apostrophe
405+ , accentedChar
406+ , specialChar
407+ , url
408+ , link
409+ , image
410+ , footnote
411+ , rawLaTeXInline
412+ , escapedChar
413+ , unescapedChar
414+ , str
415+ , endline
416+ , whitespace ] <?> "inline"
424417
425418accentedChar = normalAccentedChar <|> specialAccentedChar
426419
427−normalAccentedChar = try (do
420+normalAccentedChar = try $ do
428421 char '\\'
429422 accent <- oneOf "'`^\"~"
430− character <- choice [ between (char '{') (char '}') anyChar, anyChar ]
423+ character <- (try $ char '{' >> alphaNum >>~ char '}') <|> alphaNum
431424 let table = fromMaybe [] $ lookup character accentTable
432425 let result = case lookup accent table of
433426 Just num -> chr num
434427 Nothing -> '?'
435− return (Str [result]))
428+ return $ Str [result]
436429
437430-- an association list of letters and association list of accents
438431-- and decimal character numbers.
@@ -451,245 +444,179 @@ accentTable =
451444 ('u', [('`', 249), ('\'', 250), ('^', 251), ('"', 252)]) ]
452445
453446specialAccentedChar = choice [ ccedil, aring, iuml, szlig, aelig,
454− oslash, pound, euro, copyright, sect ]
447+ oslash, pound, euro, copyright, sect ]
455448
456−ccedil = try (do
449+ccedil = try $ do
457450 char '\\'
458451 letter <- oneOfStrings ["cc", "cC"]
459452 let num = if letter == "cc" then 231 else 199
460− return (Str [chr num]))
453+ return $ Str [chr num]
461454
462−aring = try (do
455+aring = try $ do
463456 char '\\'
464457 letter <- oneOfStrings ["aa", "AA"]
465458 let num = if letter == "aa" then 229 else 197
466− return (Str [chr num]))
459+ return $ Str [chr num]
467460
468−iuml = try (do
469− string "\\\""
470− oneOfStrings ["\\i", "{\\i}"]
471− return (Str [chr 239]))
461+iuml = try (string "\\\"") >> oneOfStrings ["\\i", "{\\i}"] >>
462+ return (Str [chr 239])
472463
473−icirc = try (do
474− string "\\^"
475− oneOfStrings ["\\i", "{\\i}"]
476− return (Str [chr 238]))
464+icirc = try (string "\\^") >> oneOfStrings ["\\i", "{\\i}"] >>
465+ return (Str [chr 238])
477466
478−szlig = try (do
479− string "\\ss"
480− return (Str [chr 223]))
467+szlig = try (string "\\ss") >> return (Str [chr 223])
481468
482−oslash = try (do
469+oslash = try $ do
483470 char '\\'
484471 letter <- choice [char 'o', char 'O']
485472 let num = if letter == 'o' then 248 else 216
486− return (Str [chr num]))
473+ return $ Str [chr num]
487474
488−aelig = try (do
475+aelig = try $ do
489476 char '\\'
490477 letter <- oneOfStrings ["ae", "AE"]
491478 let num = if letter == "ae" then 230 else 198
492− return (Str [chr num]))
479+ return $ Str [chr num]
493480
494−pound = try (do
495− string "\\pounds"
496− return (Str [chr 163]))
481+pound = try (string "\\pounds") >> return (Str [chr 163])
497482
498−euro = try (do
499− string "\\euro"
500− return (Str [chr 8364]))
483+euro = try (string "\\euro") >> return (Str [chr 8364])
501484
502−copyright = try (do
503− string "\\copyright"
504− return (Str [chr 169]))
485+copyright = try (string "\\copyright") >> return (Str [chr 169])
505486
506−sect = try (do
507− string "\\S"
508− return (Str [chr 167]))
487+sect = try (string "\\S") >> return (Str [chr 167])
509488
510489escapedChar = do
511490 result <- escaped (oneOf " $%&_#{}\n")
512− return (if result == Str "\n" then Str " " else result)
491+ return $ if result == Str "\n" then Str " " else result
513492
514−unescapedChar = do -- ignore standalone, nonescaped special characters
515− oneOf "$^&_#{}|<>"
516− return (Str "")
493+-- ignore standalone, nonescaped special characters
494+unescapedChar = oneOf "$^&_#{}|<>" >> return (Str "")
517495
518496specialChar = choice [ backslash, tilde, caret, bar, lt, gt ]
519497
520−backslash = try (do
521− string "\\textbackslash"
522− return (Str "\\"))
498+backslash = try (string "\\textbackslash") >> return (Str "\\")
523499
524−tilde = try (do
525− string "\\ensuremath{\\sim}"
526− return (Str "~"))
500+tilde = try (string "\\ensuremath{\\sim}") >> return (Str "~")
527501
528−caret = try (do
529− string "\\^{}"
530− return (Str "^"))
502+caret = try (string "\\^{}") >> return (Str "^")
531503
532−bar = try (do
533− string "\\textbar"
534− return (Str "\\"))
504+bar = try (string "\\textbar") >> return (Str "\\")
535505
536−lt = try (do
537− string "\\textless"
538− return (Str "<"))
506+lt = try (string "\\textless") >> return (Str "<")
539507
540−gt = try (do
541− string "\\textgreater"
542− return (Str ">"))
508+gt = try (string "\\textgreater") >> return (Str ">")
543509
544−code = try (do
510+code = try $ do
545511 string "\\verb"
546512 marker <- anyChar
547513 result <- manyTill anyChar (char marker)
548− let result' = removeLeadingTrailingSpace result
549− return (Code result'))
514+ return $ Code $ removeLeadingTrailingSpace result
550515
551−emph = try (do
552− oneOfStrings [ "\\emph{", "\\textit{" ]
553− result <- manyTill inline (char '}')
554− return (Emph result))
516+emph = try $ oneOfStrings [ "\\emph{", "\\textit{" ] >>
517+ manyTill inline (char '}') >>= return . Emph
555518
556−strikeout = try $ do
557− string "\\sout{"
558− result <- manyTill inline (char '}')
559− return (Strikeout result)
519+strikeout = try $ string "\\sout{" >> manyTill inline (char '}') >>=
520+ return . Strikeout
560521
561−superscript = try $ do
562− string "\\textsuperscript{"
563− result <- manyTill inline (char '}')
564− return (Superscript result)
522+superscript = try $ string "\\textsuperscript{" >>
523+ manyTill inline (char '}') >>= return . Superscript
565524
566525-- note: \textsubscript isn't a standard latex command, but we use
567526-- a defined version in pandoc.
568−subscript = try $ do
569− string "\\textsubscript{"
570− result <- manyTill inline (char '}')
571− return (Subscript result)
527+subscript = try $ string "\\textsubscript{" >> manyTill inline (char '}') >>=
528+ return . Subscript
572529
573−apostrophe = do
574− char '\''
575− return Apostrophe
530+apostrophe = char '\'' >> return Apostrophe
576531
577−quoted = do
578− doubleQuoted <|> singleQuoted
532+quoted = doubleQuoted <|> singleQuoted
579533
580−singleQuoted = try (do
581− result <- enclosed singleQuoteStart singleQuoteEnd inline
582− return $ Quoted SingleQuote $ normalizeSpaces result)
534+singleQuoted = enclosed singleQuoteStart singleQuoteEnd inline >>=
535+ return . Quoted SingleQuote . normalizeSpaces
583536
584−doubleQuoted = try (do
585− result <- enclosed doubleQuoteStart doubleQuoteEnd inline
586− return $ Quoted DoubleQuote $ normalizeSpaces result)
537+doubleQuoted = enclosed doubleQuoteStart doubleQuoteEnd inline >>=
538+ return . Quoted DoubleQuote . normalizeSpaces
587539
588540singleQuoteStart = char '`'
589541
590−singleQuoteEnd = char '\'' >> notFollowedBy alphaNum
542+singleQuoteEnd = try $ char '\'' >> notFollowedBy alphaNum
591543
592544doubleQuoteStart = string "``"
593545
594546doubleQuoteEnd = string "''"
595547
596−ellipses = try (do
597− string "\\ldots"
598− option "" (try (string "{}"))
599− return Ellipses)
548+ellipses = try $ string "\\ldots" >> optional (try (string "{}")) >>
549+ return Ellipses
600550
601−enDash = try (do
602− string "--"
603− notFollowedBy (char '-')
604− return EnDash)
551+enDash = try (string "--") >> return EnDash
605552
606−emDash = try (do
607− string "---"
608− return EmDash)
553+emDash = try (string "---") >> return EmDash
609554
610−hyphen = do
611− char '-'
612− return (Str "-")
555+hyphen = char '-' >> return (Str "-")
613556
614−lab = try (do
557+lab = try $ do
615558 string "\\label{"
616559 result <- manyTill anyChar (char '}')
617− return (Str ("(" ++ result ++ ")")))
560+ return $ Str $ "(" ++ result ++ ")"
618561
619−ref = try (do
620− string "\\ref{"
621− result <- manyTill anyChar (char '}')
622− return (Str (result)))
562+ref = try (string "\\ref{") >> manyTill anyChar (char '}') >>= return . Str
623563
624−strong = try (do
625− string "\\textbf{"
626− result <- manyTill inline (char '}')
627− return (Strong result))
564+strong = try (string "\\textbf{") >> manyTill inline (char '}') >>=
565+ return . Strong
628566
629−whitespace = do
630− many1 (oneOf "~ \t")
631− return Space
567+whitespace = many1 (oneOf "~ \t") >> return Space
632568
633569-- hard line break
634−linebreak = try (do
635− string "\\\\"
636− return LineBreak)
570+linebreak = try (string "\\\\") >> return LineBreak
637571
638−spacer = try $ do
639− string "\\,"
640− return (Str "")
572+spacer = try (string "\\,") >> return (Str "")
641573
642−str = do
643− result <- many1 (noneOf specialChars)
644− return (Str result)
574+str = many1 (noneOf specialChars) >>= return . Str
645575
646576-- endline internal to paragraph
647−endline = try (do
648− newline
649− notFollowedBy blankline
650− return Space)
577+endline = try $ newline >> notFollowedBy blankline >> return Space
651578
652579-- math
653580math = math1 <|> math2 <?> "math"
654581
655−math1 = try (do
582+math1 = try $ do
656583 char '$'
657584 result <- many (noneOf "$")
658585 char '$'
659− return (TeX ("$" ++ result ++ "$")))
586+ return $ TeX ("$" ++ result ++ "$")
660587
661−math2 = try (do
588+math2 = try $ do
662589 string "\\("
663590 result <- many (noneOf "$")
664591 string "\\)"
665− return (TeX ("$" ++ result ++ "$")))
592+ return $ TeX ("$" ++ result ++ "$")
666593
667594--
668595-- links and images
669596--
670597
671−url = try (do
598+url = try $ do
672599 string "\\url"
673600 url <- charsInBalanced '{' '}'
674− return (Link [Code url] (url, "")))
601+ return $ Link [Code url] (url, "")
675602
676−link = try (do
603+link = try $ do
677604 string "\\href{"
678605 url <- manyTill anyChar (char '}')
679606 char '{'
680607 label <- manyTill inline (char '}')
681− return (Link (normalizeSpaces label) (url, "")))
608+ return $ Link (normalizeSpaces label) (url, "")
682609
683−image = try (do
610+image = try $ do
684611 ("includegraphics", _, args) <- command
685612 let args' = filter isArg args -- filter out options
686613 let src = if null args' then
687614 ("", "")
688615 else
689616 (stripFirstAndLast (head args'), "")
690− return (Image [Str "image"] src))
617+ return $ Image [Str "image"] src
691618
692−footnote = try (do
619+footnote = try $ do
693620 (name, _, (contents:[])) <- command
694621 if ((name == "footnote") || (name == "thanks"))
695622 then string ""
@@ -700,16 +627,15 @@ footnote = try (do
700627 setInput $ contents'
701628 blocks <- parseBlocks
702629 setInput rest
703− return (Note blocks))
630+ return $ Note blocks
704631
705632-- | Parse any LaTeX command and return it in a raw TeX inline element.
706633rawLaTeXInline :: GenParser Char ParserState Inline
707−rawLaTeXInline = try (do
634+rawLaTeXInline = try $ do
708635 (name, star, args) <- command
709− let argStr = concat args
710636 state <- getState
711637 if ((name == "begin") || (name == "end") || (name == "item"))
712638 then fail "not an inline command"
713639 else string ""
714− return (TeX ("\\" ++ name ++ star ++ argStr)))
640+ return $ TeX ("\\" ++ name ++ star ++ concat args)
715641
src/Text/Pandoc/Readers/Markdown.hs+273−389
@@ -31,28 +31,24 @@ module Text.Pandoc.Readers.Markdown (
3131 readMarkdown
3232 ) where
3333
34−import Data.List ( findIndex, sortBy, transpose, isSuffixOf, intersect, lookup )
34+import Data.List ( transpose, isSuffixOf, lookup, sortBy )
35+import Data.Ord ( comparing )
3536import Data.Char ( isAlphaNum )
36−import Text.Pandoc.ParserCombinators
3737import Text.Pandoc.Definition
38−import Text.Pandoc.Readers.LaTeX ( rawLaTeXInline, rawLaTeXEnvironment )
3938import Text.Pandoc.Shared
39+import Text.Pandoc.Readers.LaTeX ( rawLaTeXInline, rawLaTeXEnvironment )
4040import Text.Pandoc.Readers.HTML ( rawHtmlBlock,
4141 anyHtmlBlockTag, anyHtmlInlineTag,
4242 anyHtmlTag, anyHtmlEndTag,
4343 htmlEndTag, extractTagType,
4444 htmlBlockElement )
45−import Text.Pandoc.Entities ( characterEntity, decodeEntities )
45+import Text.Pandoc.CharacterReferences ( decodeCharacterReferences )
4646import Text.ParserCombinators.Parsec
4747
4848-- | Read markdown from an input string and return a Pandoc document.
4949readMarkdown :: ParserState -> String -> Pandoc
5050readMarkdown state str = (readWith parseMarkdown) state (str ++ "\n\n")
5151
52−-- | Parse markdown string with default options and print result (for testing).
53−testString :: String -> IO ()
54−testString = testStringWith parseMarkdown
55−
5652--
5753-- Constants and data structure definitions
5854--
@@ -70,19 +66,16 @@ specialChars = "\\[]*_~`<>$!^-.&'\""
7066-- auxiliary functions
7167--
7268
73−-- | Skip a single endline if there is one.
74−skipEndline = option Space endline
75−
7669indentSpaces = try $ do
7770 state <- getState
7871 let tabStop = stateTabStop state
7972 try (count tabStop (char ' ')) <|>
80− (do{many (char ' '); string "\t"}) <?> "indentation"
73+ (many (char ' ') >> string "\t") <?> "indentation"
8174
8275nonindentSpaces = do
8376 state <- getState
8477 let tabStop = stateTabStop state
85− choice (map (\n -> (try (count n (char ' ')))) (reverse [0..(tabStop - 1)]))
78+ choice $ map (\n -> (try (count n (char ' ')))) $ reverse [0..(tabStop - 1)]
8679
8780-- | Fail unless we're at beginning of a line.
8881failUnlessBeginningOfLine = do
@@ -94,20 +87,21 @@ failUnlessSmart = do
9487 state <- getState
9588 if stateSmart state then return () else fail "Smart typography feature"
9689
90+-- | Parse an inline Str element with a given content.
91+inlineString str = try $ do
92+ (Str res) <- inline
93+ if res == str then return res else fail $ "unexpected Str content"
94+
9795-- | Parse a sequence of inline elements between a string
9896-- @opener@ and a string @closer@, including inlines
9997-- between balanced pairs of @opener@ and a @closer@.
10098inlinesInBalanced :: String -> String -> GenParser Char ParserState [Inline]
10199inlinesInBalanced opener closer = try $ do
102− let openerSymbol = try $ do
103− res <- inline
104− if res == Str opener
105− then return res
106− else pzero
107− try (string opener)
108− result <- manyTill ( (do lookAhead openerSymbol
109− bal <- inlinesInBalanced opener closer
110− return $ [Str opener] ++ bal ++ [Str closer])
100+ string opener
101+ result <- manyTill ( (do lookAhead (inlineString opener)
102+ -- because it might be a link...
103+ bal <- inlinesInBalanced opener closer
104+ return $ [Str opener] ++ bal ++ [Str closer])
111105 <|> (count 1 inline))
112106 (try (string closer))
113107 return $ concat result
@@ -116,59 +110,55 @@ inlinesInBalanced opener closer = try $ do
116110-- document structure
117111--
118112
119−titleLine = try (do
120− char '%'
121− skipSpaces
122− line <- manyTill inline newline
123− return line)
113+titleLine = try $ char '%' >> skipSpaces >> manyTill inline newline
124114
125−authorsLine = try (do
115+authorsLine = try $ do
126116 char '%'
127117 skipSpaces
128118 authors <- sepEndBy (many1 (noneOf ",;\n")) (oneOf ",;")
129119 newline
130− return (map (decodeEntities . removeLeadingTrailingSpace) authors))
120+ return $ map (decodeCharacterReferences . removeLeadingTrailingSpace) authors
131121
132−dateLine = try (do
122+dateLine = try $ do
133123 char '%'
134124 skipSpaces
135125 date <- many (noneOf "\n")
136126 newline
137− return (decodeEntities $ removeTrailingSpace date))
127+ return $ decodeCharacterReferences $ removeTrailingSpace date
138128
139−titleBlock = try (do
129+titleBlock = try $ do
140130 failIfStrict
141131 title <- option [] titleLine
142132 author <- option [] authorsLine
143133 date <- option "" dateLine
144− option "" blanklines
145− return (title, author, date))
134+ optional blanklines
135+ return (title, author, date)
146136
147137parseMarkdown = do
148− updateState (\state -> state { stateParseRaw = True }) -- markdown allows raw HTML
138+ -- markdown allows raw HTML
139+ updateState (\state -> state { stateParseRaw = True })
149140 (title, author, date) <- option ([],[],"") titleBlock
150141 -- go through once just to get list of reference keys
151− refs <- manyTill (referenceKey <|> (do l <- lineClump
152− return (LineClump l))) eof
142+ refs <- manyTill (referenceKey <|> (lineClump >>= return . LineClump)) eof
153143 let keys = map (\(KeyBlock label target) -> (label, target)) $
154144 filter isKeyBlock refs
155145 let rawlines = map (\(LineClump ln) -> ln) $ filter isLineClump refs
156146 setInput $ concat rawlines -- with keys stripped out
157147 updateState (\state -> state { stateKeys = keys })
158− -- now go through for notes
159− refs <- manyTill (noteBlock <|> (do l <- lineClump
160− return (LineClump l))) eof
148+ -- now go through for notes (which may contain references - hence 2nd pass)
149+ refs <- manyTill (noteBlock <|> (lineClump >>= return . LineClump)) eof
161150 let notes = map (\(NoteBlock label blocks) -> (label, blocks)) $
162151 filter isNoteBlock refs
163152 let rawlines = map (\(LineClump ln) -> ln) $ filter isLineClump refs
164− setInput $ concat rawlines -- with note blocks and keys stripped out
153+ -- go through a 3rd time, with note blocks and keys stripped out
154+ setInput $ concat rawlines
165155 updateState (\state -> state { stateNotes = notes })
166− blocks <- parseBlocks -- go through again, for real
156+ blocks <- parseBlocks
167157 let blocks' = filter (/= Null) blocks
168− return (Pandoc (Meta title author date) blocks')
158+ return $ Pandoc (Meta title author date) blocks'
169159
170160--
171−-- initial pass for references
161+-- initial pass for references and notes
172162--
173163
174164referenceKey = try $ do
@@ -176,9 +166,9 @@ referenceKey = try $ do
176166 label <- reference
177167 char ':'
178168 skipSpaces
179− option ' ' (char '<')
169+ optional (char '<')
180170 src <- many (noneOf "> \n\t")
181− option ' ' (char '>')
171+ optional (char '>')
182172 tit <- option "" title
183173 blanklines
184174 return $ KeyBlock label (removeTrailingSpace src, tit)
@@ -189,33 +179,28 @@ noteMarker = try $ do
189179 manyTill (noneOf " \t\n") (char ']')
190180
191181rawLine = try $ do
192− notFollowedBy' blankline
182+ notFollowedBy blankline
193183 notFollowedBy' noteMarker
194184 contents <- many1 nonEndline
195− end <- option "" (do
196− newline
197− option "" indentSpaces
198− return "\n")
199− return (contents ++ end)
185+ end <- option "" (newline >> optional indentSpaces >> return "\n")
186+ return $ contents ++ end
200187
201−rawLines = do
202− lines <- many1 rawLine
203− return (concat lines)
188+rawLines = many1 rawLine >>= return . concat
204189
205190noteBlock = try $ do
206191 failIfStrict
207192 ref <- noteMarker
208193 char ':'
209− option ' ' blankline
210− option "" indentSpaces
211− raw <- sepBy rawLines (try (do {blankline; indentSpaces}))
212− option "" blanklines
194+ optional blankline
195+ optional indentSpaces
196+ raw <- sepBy rawLines (try (blankline >> indentSpaces))
197+ optional blanklines
213198 -- parse the extracted text, which may contain various block elements:
214199 rest <- getInput
215200 setInput $ (joinWithSep "\n" raw) ++ "\n\n"
216201 contents <- parseBlocks
217202 setInput rest
218− return (NoteBlock ref contents)
203+ return $ NoteBlock ref contents
219204
220205--
221206-- parsing blocks
@@ -239,48 +224,39 @@ block = choice [ header
239224-- header blocks
240225--
241226
242−header = choice [ setextHeader, atxHeader ] <?> "header"
227+header = setextHeader <|> atxHeader <?> "header"
243228
244−atxHeader = try (do
229+atxHeader = try $ do
245230 lead <- many1 (char '#')
246− notFollowedBy (char '.') -- this would be a list
247− notFollowedBy (char ')')
231+ notFollowedBy (char '.' <|> char ')') -- this would be a list
248232 skipSpaces
249233 txt <- manyTill inline atxClosing
250− return (Header (length lead) (normalizeSpaces txt)))
234+ return $ Header (length lead) (normalizeSpaces txt)
251235
252−atxClosing = try (do
253− skipMany (char '#')
254− skipSpaces
255− newline
256− option "" blanklines)
236+atxClosing = try $ skipMany (char '#') >> skipSpaces >> newline >>
237+ option "" blanklines
257238
258239setextHeader = choice $
259− map (\x -> setextH x) (enumFromTo 1 (length setextHChars))
240+ map (\x -> setextH x) $ enumFromTo 1 (length setextHChars)
260241
261−setextH n = try (do
242+setextH n = try $ do
262243 txt <- many1Till inline newline
263244 many1 (char (setextHChars !! (n-1)))
264245 skipSpaces
265246 newline
266− option "" blanklines
267− return (Header n (normalizeSpaces txt)))
247+ optional blanklines
248+ return $ Header n (normalizeSpaces txt)
268249
269250--
270251-- hrule block
271252--
272253
273−hruleWith chr = try (do
274− skipSpaces
275− char chr
276− skipSpaces
277− char chr
278− skipSpaces
279− char chr
280− skipMany (oneOf (chr:spaceChars))
254+hruleWith chr = try $ do
255+ count 3 (skipSpaces >> char chr)
256+ skipMany (skipSpaces >> char chr)
281257 newline
282− option "" blanklines
283− return HorizontalRule)
258+ optional blanklines
259+ return HorizontalRule
284260
285261hrule = choice (map hruleWith hruleChars) <?> "hrule"
286262
@@ -288,67 +264,55 @@ hrule = choice (map hruleWith hruleChars) <?> "hrule"
288264-- code blocks
289265--
290266
291−indentedLine = try (do
267+indentedLine = try $ do
292268 indentSpaces
293269 result <- manyTill anyChar newline
294− return (result ++ "\n"))
270+ return $ result ++ "\n"
295271
296272-- two or more indented lines, possibly separated by blank lines
297−indentedBlock = try (do
273+indentedBlock = try $ do
298274 res1 <- indentedLine
299275 blanks <- many blankline
300− res2 <- choice [indentedBlock, indentedLine]
301− return (res1 ++ blanks ++ res2))
276+ res2 <- indentedBlock <|> indentedLine
277+ return $ res1 ++ blanks ++ res2
302278
303−codeBlock = do
304− result <- choice [indentedBlock, indentedLine]
305− option "" blanklines
306− return (CodeBlock (stripTrailingNewlines result))
279+codeBlock = (indentedBlock <|> indentedLine) >>~ optional blanklines >>=
280+ return . CodeBlock . stripTrailingNewlines
307281
308282--
309283-- block quotes
310284--
311285
312−emacsBoxQuote = try (do
286+emacsBoxQuote = try $ do
313287 failIfStrict
314288 string ",----"
315289 manyTill anyChar newline
316− raw <- manyTill (try (do
317− char '|'
318− option ' ' (char ' ')
319− result <- manyTill anyChar newline
320− return result))
321− (string "`----")
322− manyTill anyChar newline
323− option "" blanklines
324− return raw)
290+ raw <- manyTill
291+ (try (char '|' >> optional (char ' ') >> manyTill anyChar newline))
292+ (try (string "`----"))
293+ blanklines
294+ return raw
325295
326−emailBlockQuoteStart = try (do
327− nonindentSpaces
328− char '>'
329− option ' ' (char ' ')
330− return "> ")
296+emailBlockQuoteStart = try $ nonindentSpaces >> char '>' >>~ optional (char ' ')
331297
332−emailBlockQuote = try (do
298+emailBlockQuote = try $ do
333299 emailBlockQuoteStart
334− raw <- sepBy (many (choice [nonEndline,
335− (try (do
336− endline
337− notFollowedBy' emailBlockQuoteStart
338− return '\n'))]))
339− (try (do {newline; emailBlockQuoteStart}))
340− newline <|> (do{ eof; return '\n' })
341− option "" blanklines
342− return raw)
300+ raw <- sepBy (many (nonEndline <|>
301+ (try (endline >> notFollowedBy emailBlockQuoteStart >>
302+ return '\n'))))
303+ (try (newline >> emailBlockQuoteStart))
304+ newline <|> (eof >> return '\n')
305+ optional blanklines
306+ return raw
343307
344308blockQuote = do
345− raw <- choice [ emailBlockQuote, emacsBoxQuote ]
309+ raw <- emailBlockQuote <|> emacsBoxQuote
346310 -- parse the extracted block, which may contain various block elements:
347311 rest <- getInput
348312 setInput $ (joinWithSep "\n" raw) ++ "\n\n"
349313 contents <- parseBlocks
350314 setInput rest
351− return (BlockQuote contents)
315+ return $ BlockQuote contents
352316
353317--
354318-- list blocks
@@ -357,7 +321,7 @@ blockQuote = do
357321list = choice [ bulletList, orderedList, definitionList ] <?> "list"
358322
359323bulletListStart = try $ do
360− option ' ' newline -- if preceded by a Plain block in a list context
324+ optional newline -- if preceded by a Plain block in a list context
361325 nonindentSpaces
362326 notFollowedBy' hrule -- because hrules start out just like lists
363327 oneOf bulletListMarkers
@@ -365,7 +329,7 @@ bulletListStart = try $ do
365329 skipSpaces
366330
367331anyOrderedListStart = try $ do
368− option ' ' newline -- if preceded by a Plain block in a list context
332+ optional newline -- if preceded by a Plain block in a list context
369333 nonindentSpaces
370334 state <- getState
371335 if stateStrict state
@@ -375,7 +339,7 @@ anyOrderedListStart = try $ do
375339 else anyOrderedListMarker
376340
377341orderedListStart style delim = try $ do
378− option ' ' newline -- if preceded by a Plain block in a list context
342+ optional newline -- if preceded by a Plain block in a list context
379343 nonindentSpaces
380344 state <- getState
381345 if stateStrict state
@@ -387,40 +351,39 @@ orderedListStart style delim = try $ do
387351 skipSpaces
388352
389353-- parse a line of a list item (start = parser for beginning of list item)
390−listLine start = try (do
354+listLine start = try $ do
391355 notFollowedBy' start
392356 notFollowedBy blankline
393− notFollowedBy' (do
394− indentSpaces
395− many (spaceChar)
396− choice [bulletListStart, anyOrderedListStart >> return ()])
357+ notFollowedBy' (do indentSpaces
358+ many (spaceChar)
359+ bulletListStart <|> (anyOrderedListStart >> return ()))
397360 line <- manyTill anyChar newline
398− return (line ++ "\n"))
361+ return $ line ++ "\n"
399362
400363-- parse raw text for one list item, excluding start marker and continuations
401−rawListItem start = try (do
364+rawListItem start = try $ do
402365 start
403366 result <- many1 (listLine start)
404367 blanks <- many blankline
405− return ((concat result) ++ blanks))
368+ return $ concat result ++ blanks
406369
407370-- continuation of a list item - indented and separated by blankline
408371-- or (in compact lists) endline.
409372-- note: nested lists are parsed as continuations
410−listContinuation start = try (do
373+listContinuation start = try $ do
411374 lookAhead indentSpaces
412375 result <- many1 (listContinuationLine start)
413376 blanks <- many blankline
414− return ((concat result) ++ blanks))
377+ return $ concat result ++ blanks
415378
416−listContinuationLine start = try (do
417− notFollowedBy' blankline
379+listContinuationLine start = try $ do
380+ notFollowedBy blankline
418381 notFollowedBy' start
419− option "" indentSpaces
382+ optional indentSpaces
420383 result <- manyTill anyChar newline
421− return (result ++ "\n"))
384+ return $ result ++ "\n"
422385
423−listItem start = try (do
386+listItem start = try $ do
424387 first <- rawListItem start
425388 continuations <- many (listContinuation start)
426389 -- parsing with ListItemState forces markers at beginning of lines to
@@ -436,18 +399,15 @@ listItem start = try (do
436399 contents <- parseBlocks
437400 setInput rest
438401 updateState (\st -> st {stateParserContext = oldContext})
439− return contents)
402+ return contents
440403
441−orderedList = try (do
404+orderedList = do
442405 (start, style, delim) <- lookAhead anyOrderedListStart
443406 items <- many1 (listItem (orderedListStart style delim))
444− let items' = compactify items
445− return (OrderedList (start, style, delim) items'))
407+ return $ OrderedList (start, style, delim) $ compactify items
446408
447−bulletList = try (do
448− items <- many1 (listItem bulletListStart)
449− let items' = compactify items
450− return (BulletList items'))
409+bulletList = many1 (listItem bulletListStart) >>=
410+ return . BulletList . compactify
451411
452412-- definition lists
453413
@@ -470,9 +430,9 @@ defRawBlock = try $ do
470430 char ':'
471431 state <- getState
472432 let tabStop = stateTabStop state
473− try (count (tabStop - 1) (char ' ')) <|> (do{many (char ' '); string "\t"})
433+ try (count (tabStop - 1) (char ' ')) <|> (many (char ' ') >> string "\t")
474434 firstline <- anyLine
475− rawlines <- many (do {notFollowedBy' blankline; indentSpaces; anyLine})
435+ rawlines <- many (notFollowedBy blankline >> indentSpaces >> anyLine)
476436 trailing <- option "" blanklines
477437 return $ firstline ++ "\n" ++ unlines rawlines ++ trailing
478438
@@ -488,71 +448,62 @@ definitionList = do
488448-- paragraph block
489449--
490450
491−para = try (do
451+para = try $ do
492452 result <- many1 inline
493453 newline
494454 st <- getState
495455 if stateStrict st
496− then choice [lookAhead blockQuote, lookAhead header,
497− (do{blanklines; return Null})]
498− else choice [(do{lookAhead emacsBoxQuote; return Null}),
499− (do{blanklines; return Null})]
500− let result' = normalizeSpaces result
501− return (Para result'))
502−
503−plain = do
504− result <- many1 inline
505− let result' = normalizeSpaces result
506− return (Plain result')
456+ then choice [ lookAhead blockQuote, lookAhead header,
457+ (blanklines >> return Null) ]
458+ else choice [ lookAhead emacsBoxQuote >> return Null,
459+ (blanklines >> return Null) ]
460+ return $ Para $ normalizeSpaces result
461+
462+plain = many1 inline >>= return . Plain . normalizeSpaces
507463
508464--
509465-- raw html
510466--
511467
512−htmlElement = choice [strictHtmlBlock,
513− htmlBlockElement] <?> "html element"
468+htmlElement = strictHtmlBlock <|> htmlBlockElement <?> "html element"
514469
515470htmlBlock = do
516471 st <- getState
517472 if stateStrict st
518− then do
519− failUnlessBeginningOfLine
520− first <- htmlElement
521− finalSpace <- many (oneOf spaceChars)
522− finalNewlines <- many newline
523− return (RawHtml (first ++ finalSpace ++ finalNewlines))
473+ then try $ do failUnlessBeginningOfLine
474+ first <- htmlElement
475+ finalSpace <- many (oneOf spaceChars)
476+ finalNewlines <- many newline
477+ return $ RawHtml $ first ++ finalSpace ++ finalNewlines
524478 else rawHtmlBlocks
525479
526480-- True if tag is self-closing
527481isSelfClosing tag =
528482 isSuffixOf "/>" $ filter (\c -> (not (c `elem` " \n\t"))) tag
529483
530−strictHtmlBlock = try (do
484+strictHtmlBlock = try $ do
531485 tag <- anyHtmlBlockTag
532486 let tag' = extractTagType tag
533487 if isSelfClosing tag || tag' == "hr"
534488 then return tag
535− else do
536− contents <- many (do{notFollowedBy' (htmlEndTag tag');
537− htmlElement <|> (count 1 anyChar)})
538− end <- htmlEndTag tag'
539− return $ tag ++ (concat contents) ++ end)
489+ else do contents <- many (notFollowedBy' (htmlEndTag tag') >>
490+ (htmlElement <|> (count 1 anyChar)))
491+ end <- htmlEndTag tag'
492+ return $ tag ++ concat contents ++ end
540493
541−rawHtmlBlocks = try (do
494+rawHtmlBlocks = try $ do
542495 htmlBlocks <- many1 rawHtmlBlock
543496 let combined = concatMap (\(RawHtml str) -> str) htmlBlocks
544− let combined' = if (last combined == '\n')
497+ let combined' = if not (null combined) && last combined == '\n'
545498 then init combined -- strip extra newline
546499 else combined
547− return (RawHtml combined'))
500+ return $ RawHtml combined'
548501
549502--
550503-- LaTeX
551504--
552505
553−rawLaTeXEnvironment' = do
554− failIfStrict
555− rawLaTeXEnvironment
506+rawLaTeXEnvironment' = failIfStrict >> rawLaTeXEnvironment
556507
557508--
558509-- Tables
@@ -560,54 +511,46 @@ rawLaTeXEnvironment' = do
560511
561512-- Parse a dashed line with optional trailing spaces; return its length
562513-- and the length including trailing space.
563−dashedLine ch = do
564− dashes <- many1 (char ch)
565− sp <- many spaceChar
566− return $ (length dashes, length $ dashes ++ sp)
514+dashedLine ch = try $ do
515+ dashes <- many1 (char ch)
516+ sp <- many spaceChar
517+ return $ (length dashes, length $ dashes ++ sp)
567518
568519-- Parse a table header with dashed lines of '-' preceded by
569520-- one line of text.
570−simpleTableHeader = do
571− rawContent <- anyLine
572− initSp <- nonindentSpaces
573− dashes <- many1 (dashedLine '-')
574− newline
575− let (lengths, lines) = unzip dashes
576− let indices = scanl (+) (length initSp) lines
577− let rawHeads = tail $ splitByIndices (init indices) rawContent
578− let aligns = zipWith alignType (map (\a -> [a]) rawHeads) lengths
579− return $ (rawHeads, aligns, indices)
521+simpleTableHeader = try $ do
522+ rawContent <- anyLine
523+ initSp <- nonindentSpaces
524+ dashes <- many1 (dashedLine '-')
525+ newline
526+ let (lengths, lines) = unzip dashes
527+ let indices = scanl (+) (length initSp) lines
528+ let rawHeads = tail $ splitByIndices (init indices) rawContent
529+ let aligns = zipWith alignType (map (\a -> [a]) rawHeads) lengths
530+ return (rawHeads, aligns, indices)
580531
581532-- Parse a table footer - dashed lines followed by blank line.
582−tableFooter = try $ do
583− nonindentSpaces
584− many1 (dashedLine '-')
585− blanklines
533+tableFooter = try $ nonindentSpaces >> many1 (dashedLine '-') >> blanklines
586534
587535-- Parse a table separator - dashed line.
588−tableSep = try $ do
589− nonindentSpaces
590− many1 (dashedLine '-')
591− string "\n"
536+tableSep = try $ nonindentSpaces >> many1 (dashedLine '-') >> string "\n"
592537
593538-- Parse a raw line and split it into chunks by indices.
594539rawTableLine indices = do
595− notFollowedBy' (blanklines <|> tableFooter)
596− line <- many1Till anyChar newline
597− return $ map removeLeadingTrailingSpace $ tail $
598− splitByIndices (init indices) line
540+ notFollowedBy' (blanklines <|> tableFooter)
541+ line <- many1Till anyChar newline
542+ return $ map removeLeadingTrailingSpace $ tail $
543+ splitByIndices (init indices) line
599544
600545-- Parse a table line and return a list of lists of blocks (columns).
601−tableLine indices = try $ do
602− rawline <- rawTableLine indices
603− mapM (parseFromString (many plain)) rawline
546+tableLine indices = rawTableLine indices >>= mapM (parseFromString (many plain))
604547
605548-- Parse a multiline table row and return a list of blocks (columns).
606549multilineRow indices = try $ do
607− colLines <- many1 (rawTableLine indices)
608− option "" blanklines
609− let cols = map unlines $ transpose colLines
610− mapM (parseFromString (many plain)) cols
550+ colLines <- many1 (rawTableLine indices)
551+ optional blanklines
552+ let cols = map unlines $ transpose colLines
553+ mapM (parseFromString (many plain)) cols
611554
612555-- Calculate relative widths of table columns, based on indices
613556widthsFromIndices :: Int -- Number of columns on terminal
@@ -615,22 +558,22 @@ widthsFromIndices :: Int -- Number of columns on terminal
615558 -> [Float] -- Fractional relative sizes of columns
616559widthsFromIndices _ [] = []
617560widthsFromIndices numColumns indices =
618− let lengths = zipWith (-) indices (0:indices)
619− totLength = sum lengths
620− quotient = if totLength > numColumns
621− then fromIntegral totLength
622− else fromIntegral numColumns
623− fracs = map (\l -> (fromIntegral l) / quotient) lengths in
624− tail fracs
561+ let lengths = zipWith (-) indices (0:indices)
562+ totLength = sum lengths
563+ quotient = if totLength > numColumns
564+ then fromIntegral totLength
565+ else fromIntegral numColumns
566+ fracs = map (\l -> (fromIntegral l) / quotient) lengths in
567+ tail fracs
625568
626569-- Parses a table caption: inlines beginning with 'Table:'
627570-- and followed by blank lines.
628571tableCaption = try $ do
629− nonindentSpaces
630− string "Table:"
631− result <- many1 inline
632− blanklines
633− return $ normalizeSpaces result
572+ nonindentSpaces
573+ string "Table:"
574+ result <- many1 inline
575+ blanklines
576+ return $ normalizeSpaces result
634577
635578-- Parse a table using 'headerParser', 'lineParser', and 'footerParser'.
636579tableWith headerParser lineParser footerParser = try $ do
@@ -653,30 +596,19 @@ simpleTable = tableWith simpleTableHeader tableLine blanklines
653596multilineTable = tableWith multilineTableHeader multilineRow tableFooter
654597
655598multilineTableHeader = try $ do
656− tableSep
657− rawContent <- many1 (do{notFollowedBy' tableSep;
658− many1Till anyChar newline})
659− initSp <- nonindentSpaces
660− dashes <- many1 (dashedLine '-')
661− newline
662− let (lengths, lines) = unzip dashes
663− let indices = scanl (+) (length initSp) lines
664− let rawHeadsList = transpose $ map
665− (\ln -> tail $ splitByIndices (init indices) ln)
666− rawContent
667− let rawHeads = map (joinWithSep " ") rawHeadsList
668− let aligns = zipWith alignType rawHeadsList lengths
669− return $ ((map removeLeadingTrailingSpace rawHeads),
670− aligns, indices)
671−
672−-- Returns the longest of a list of strings.
673−longest :: [String] -> String
674−longest [] = ""
675−longest [x] = x
676−longest (x:xs) =
677− if (length x) >= (maximum $ map length xs)
678− then x
679− else longest xs
599+ tableSep
600+ rawContent <- many1 (notFollowedBy' tableSep >> many1Till anyChar newline)
601+ initSp <- nonindentSpaces
602+ dashes <- many1 (dashedLine '-')
603+ newline
604+ let (lengths, lines) = unzip dashes
605+ let indices = scanl (+) (length initSp) lines
606+ let rawHeadsList = transpose $ map
607+ (\ln -> tail $ splitByIndices (init indices) ln)
608+ rawContent
609+ let rawHeads = map (joinWithSep " ") rawHeadsList
610+ let aligns = zipWith alignType rawHeadsList lengths
611+ return ((map removeLeadingTrailingSpace rawHeads), aligns, indices)
680612
681613-- Returns an alignment type for a table, based on a list of strings
682614-- (the rows of the column header) and a number (the length of the
@@ -684,19 +616,17 @@ longest (x:xs) =
684616alignType :: [String] -> Int -> Alignment
685617alignType [] len = AlignDefault
686618alignType strLst len =
687− let str = longest $ map removeTrailingSpace strLst
688− leftSpace = if null str then False else ((str !! 0) `elem` " \t")
689− rightSpace = (length str < len || (str !! (len - 1)) `elem` " \t") in
690− case (leftSpace, rightSpace) of
619+ let str = head $ sortBy (comparing length) $
620+ map removeTrailingSpace strLst
621+ leftSpace = if null str then False else (str !! 0) `elem` " \t"
622+ rightSpace = length str < len || (str !! (len - 1)) `elem` " \t"
623+ in case (leftSpace, rightSpace) of
691624 (True, False) -> AlignRight
692625 (False, True) -> AlignLeft
693− (True, True) -> AlignCenter
626+ (True, True) -> AlignCenter
694627 (False, False) -> AlignDefault
695628
696−table = do
697− failIfStrict
698− result <- simpleTable <|> multilineTable <?> "table"
699− return result
629+table = failIfStrict >> (simpleTable <|> multilineTable) <?> "table"
700630
701631--
702632-- inline
@@ -704,7 +634,7 @@ table = do
704634
705635inline = choice [ rawLaTeXInline'
706636 , escapedChar
707− , entity
637+ , charRef
708638 , note
709639 , inlineNote
710640 , link
@@ -734,80 +664,64 @@ escapedChar = try $ do
734664 result <- if stateStrict state
735665 then oneOf "\\`*_{}[]()>#+-.!~"
736666 else satisfy (not . isAlphaNum)
737− return (Str [result])
667+ return $ Str [result]
738668
739−ltSign = try (do
669+ltSign = try $ do
740670 notFollowedBy (noneOf "<") -- continue only if it's a <
741671 notFollowedBy' rawHtmlBlocks -- don't return < if it starts html
742672 char '<'
743− return (Str ['<']))
673+ return $ Str ['<']
744674
745675specialCharsMinusLt = filter (/= '<') specialChars
746676
747677symbol = do
748678 result <- oneOf specialCharsMinusLt
749− return (Str [result])
679+ return $ Str [result]
750680
751681-- parses inline code, between n `s and n `s
752−code = try (do
682+code = try $ do
753683 starts <- many1 (char '`')
754684 let num = length starts
755685 result <- many1Till anyChar (try (count num (char '`')))
756686 -- get rid of any internal newlines
757− let result' = removeLeadingTrailingSpace $ joinWithSep " " $ lines result
758− return (Code result'))
687+ return $ Code $ removeLeadingTrailingSpace $ joinWithSep " " $ lines result
759688
760−mathWord = many1 (choice [ (noneOf " \t\n\\$"),
761− (try (do
762− c <- char '\\'
763− notFollowedBy (char '$')
764− return c))])
689+mathWord = many1 ((noneOf " \t\n\\$") <|>
690+ (try (char '\\') >>~ notFollowedBy (char '$')))
765691
766−math = try (do
692+math = try $ do
767693 failIfStrict
768694 char '$'
769695 notFollowedBy space
770696 words <- sepBy1 mathWord (many1 space)
771697 char '$'
772− return (TeX ("$" ++ (joinWithSep " " words) ++ "$")))
698+ return $ TeX ("$" ++ (joinWithSep " " words) ++ "$")
773699
774−emph = do
775− result <- choice [ (enclosed (char '*') (char '*') inline),
776− (enclosed (char '_') (char '_') inline) ]
777− return $ Emph (normalizeSpaces result)
700+emph = ((enclosed (char '*') (char '*') inline) <|>
701+ (enclosed (char '_') (char '_') inline)) >>=
702+ return . Emph . normalizeSpaces
778703
779−strong = do
780− result <- (enclosed (string "**") (string "**") inline) <|>
781− (enclosed (string "__") (string "__") inline)
782− return $ Strong (normalizeSpaces result)
704+strong = ((enclosed (string "**") (string "**") inline) <|>
705+ (enclosed (string "__") (string "__") inline)) >>=
706+ return . Strong . normalizeSpaces
783707
784−strikeout = do
785− failIfStrict
786− result <- enclosed (string "~~") (string "~~") inline
787− return $ Strikeout (normalizeSpaces result)
708+strikeout = failIfStrict >> enclosed (string "~~") (string "~~") inline >>=
709+ return . Strikeout . normalizeSpaces
788710
789−superscript = do
790− failIfStrict
791− result <- enclosed (char '^') (char '^')
792− (notFollowedBy' whitespace >> inline) -- may not contain Space
793− return $ Superscript result
711+superscript = failIfStrict >> enclosed (char '^') (char '^')
712+ (notFollowedBy' whitespace >> inline) >>= -- may not contain Space
713+ return . Superscript
794714
795−subscript = do
796− failIfStrict
797− result <- enclosed (char '~') (char '~')
798− (notFollowedBy' whitespace >> inline) -- may not contain Space
799− return $ Subscript result
715+subscript = failIfStrict >> enclosed (char '~') (char '~')
716+ (notFollowedBy' whitespace >> inline) >>= -- may not contain Space
717+ return . Subscript
800718
801−smartPunctuation = do
802− failUnlessSmart
803− choice [ quoted, apostrophe, dash, ellipses ]
719+smartPunctuation = failUnlessSmart >>
720+ choice [ quoted, apostrophe, dash, ellipses ]
804721
805−apostrophe = do
806− char '\'' <|> char '\8217'
807− return Apostrophe
722+apostrophe = (char '\'' <|> char '\8217') >> return Apostrophe
808723
809−quoted = do
810− doubleQuoted <|> singleQuoted
724+quoted = doubleQuoted <|> singleQuoted
811725
812726withQuoteContext context parser = do
813727 oldState <- getState
@@ -820,15 +734,13 @@ withQuoteContext context parser = do
820734
821735singleQuoted = try $ do
822736 singleQuoteStart
823− withQuoteContext InSingleQuote $ do
824− result <- many1Till inline singleQuoteEnd
825− return $ Quoted SingleQuote $ normalizeSpaces result
737+ withQuoteContext InSingleQuote $ many1Till inline singleQuoteEnd >>=
738+ return . Quoted SingleQuote . normalizeSpaces
826739
827740doubleQuoted = try $ do
828741 doubleQuoteStart
829− withQuoteContext InDoubleQuote $ do
830− result <- many1Till inline doubleQuoteEnd
831− return $ Quoted DoubleQuote $ normalizeSpaces result
742+ withQuoteContext InDoubleQuote $ many1Till inline doubleQuoteEnd >>=
743+ return . Quoted DoubleQuote . normalizeSpaces
832744
833745failIfInQuoteContext context = do
834746 st <- getState
@@ -836,88 +748,65 @@ failIfInQuoteContext context = do
836748 then fail "already inside quotes"
837749 else return ()
838750
839−singleQuoteStart = try $ do
751+singleQuoteStart = do
840752 failIfInQuoteContext InSingleQuote
841− char '\8216' <|> do
842− char '\''
843− notFollowedBy (oneOf ")!],.;:-? \t\n")
844− notFollowedBy (try (do -- possessive or contraction
845− oneOfStrings ["s","t","m","ve","ll","re"]
846− satisfy (not . isAlphaNum)))
847− return '\''
848−
849−singleQuoteEnd = try $ do
850− char '\'' <|> char '\8217'
851− notFollowedBy alphaNum
852−
853−doubleQuoteStart = try $ do
854− failIfInQuoteContext InDoubleQuote
855− char '"' <|> char '\8220'
856− notFollowedBy (oneOf " \t\n")
753+ char '\8216' <|>
754+ do char '\''
755+ notFollowedBy (oneOf ")!],.;:-? \t\n")
756+ notFollowedBy (try (oneOfStrings ["s","t","m","ve","ll","re"] >>
757+ satisfy (not . isAlphaNum))) -- possess/contraction
758+ return '\''
759+
760+singleQuoteEnd = (char '\'' <|> char '\8217') >> notFollowedBy alphaNum
761+
762+doubleQuoteStart = failIfInQuoteContext InDoubleQuote >>
763+ (char '"' <|> char '\8220') >>
764+ notFollowedBy (oneOf " \t\n")
857765
858766doubleQuoteEnd = char '"' <|> char '\8221'
859767
860−ellipses = try (do
861− oneOfStrings ["...", " . . . ", ". . .", " . . ."]
862− return Ellipses)
768+ellipses = try $ oneOfStrings ["...", " . . . ", ". . .", " . . ."] >>
769+ return Ellipses
863770
864771dash = enDash <|> emDash
865772
866−enDash = try (do
867− char '-'
868− notFollowedBy (noneOf "0123456789")
869− return EnDash)
773+enDash = try $ char '-' >> notFollowedBy (noneOf "0123456789") >> return EnDash
870774
871−emDash = try (do
872− skipSpaces
873− oneOfStrings ["---", "--"]
874− skipSpaces
875− return EmDash)
775+emDash = try $ skipSpaces >> oneOfStrings ["---", "--"] >>
776+ skipSpaces >> return EmDash
876777
877−whitespace = do
878− many1 (oneOf spaceChars) <?> "whitespace"
879− return Space
778+whitespace = (many1 (oneOf spaceChars) >> return Space) <?> "whitespace"
880779
881−tabchar = do
882− tab
883− return (Str "\t")
780+tabchar = tab >> return (Str "\t")
884781
885782-- hard line break
886−linebreak = try (do
887− oneOf spaceChars
888− many1 (oneOf spaceChars)
889− endline
890− return LineBreak )
783+linebreak = try $ oneOf spaceChars >> many1 (oneOf spaceChars) >>
784+ endline >> return LineBreak
891785
892786nonEndline = satisfy (/='\n')
893787
894−entity = do
895− ent <- characterEntity
896− return $ Str [ent]
897−
898788strChar = noneOf (specialChars ++ spaceChars ++ "\n")
899789
900−str = do
901− result <- many1 strChar
902− return (Str result)
790+str = many1 strChar >>= return . Str
903791
904792-- an endline character that can be treated as a space, not a structural break
905−endline = try (do
793+endline = try $ do
906794 newline
907795 notFollowedBy blankline
908796 st <- getState
909797 if stateStrict st
910798 then do
911− notFollowedBy' emailBlockQuoteStart
799+ notFollowedBy emailBlockQuoteStart
912800 notFollowedBy (char '#') -- atx header
913− notFollowedBy (try (do{manyTill anyChar newline;
914− oneOf setextHChars})) -- setext header
801+ notFollowedBy (manyTill anyChar newline >> oneOf setextHChars)
802+ -- setext header
915803 else return ()
916804 -- parse potential list-starts differently if in a list:
917− if (stateParserContext st) == ListItemState
918− then notFollowedBy' $ choice [bulletListStart, anyOrderedListStart >> return ()]
805+ if stateParserContext st == ListItemState
806+ then notFollowedBy' (bulletListStart <|>
807+ (anyOrderedListStart >> return ()))
919808 else return ()
920− return Space)
809+ return Space
921810
922811--
923812-- links
@@ -930,24 +819,23 @@ reference = notFollowedBy' (string "[^") >> -- footnote reference
930819-- source for a link, with optional title
931820source = try $ do
932821 char '('
933− option ' ' (char '<')
822+ optional (char '<')
934823 src <- many (noneOf ")> \t\n")
935− option ' ' (char '>')
824+ optional (char '>')
936825 tit <- option "" title
937826 skipSpaces
938827 char ')'
939828 return (removeTrailingSpace src, tit)
940829
941−titleWith startChar endChar = try (do
830+titleWith startChar endChar = try $ do
942831 leadingSpace <- many1 (oneOf " \t\n")
943832 if length (filter (=='\n') leadingSpace) > 1
944833 then fail "title must be separated by space and on same or next line"
945834 else return ()
946835 char startChar
947− tit <- manyTill anyChar (try (do char endChar
948− skipSpaces
949− notFollowedBy (noneOf ")\n")))
950− return $ decodeEntities tit)
836+ tit <- manyTill anyChar (try (char endChar >> skipSpaces >>
837+ notFollowedBy (noneOf ")\n")))
838+ return $ decodeCharacterReferences tit
951839
952840title = choice [ titleWith '(' ')',
953841 titleWith '"' '"',
@@ -955,22 +843,20 @@ title = choice [ titleWith '(' ')',
955843
956844link = choice [explicitLink, referenceLink] <?> "link"
957845
958−explicitLink = try (do
846+explicitLink = try $ do
959847 label <- reference
960848 src <- source
961− return (Link label src))
849+ return $ Link label src
962850
963851-- a link like [this][ref] or [this][] or [this]
964852referenceLink = try $ do
965853 label <- reference
966− ref <- option [] (try (do skipSpaces
967− option ' ' newline
968− skipSpaces
969− reference))
854+ ref <- option [] (try (skipSpaces >> optional newline >>
855+ skipSpaces >> reference))
970856 let ref' = if null ref then label else ref
971857 state <- getState
972858 case lookupKeySrc (stateKeys state) ref' of
973− Nothing -> fail "no corresponding key"
859+ Nothing -> fail "no corresponding key"
974860 Just target -> return (Link label target)
975861
976862autoLink = autoLinkEmail <|> autoLinkRegular
@@ -992,10 +878,10 @@ autoLinkRegular = try $ do
992878 let src = prot ++ rest
993879 return $ Link [Code src] (src, "")
994880
995−image = try (do
881+image = try $ do
996882 char '!'
997883 (Link label src) <- link
998− return (Image label src))
884+ return $ Image label src
999885
1000886note = try $ do
1001887 failIfStrict
@@ -1003,23 +889,21 @@ note = try $ do
1003889 state <- getState
1004890 let notes = stateNotes state
1005891 case lookup ref notes of
1006− Nothing -> fail "note not found"
1007− Just contents -> return (Note contents)
892+ Nothing -> fail "note not found"
893+ Just contents -> return $ Note contents
1008894
1009895inlineNote = try $ do
1010896 failIfStrict
1011897 char '^'
1012898 contents <- inlinesInBalanced "[" "]"
1013− return (Note [Para contents])
899+ return $ Note [Para contents]
1014900
1015−rawLaTeXInline' = do
1016− failIfStrict
1017− rawLaTeXInline
901+rawLaTeXInline' = failIfStrict >> rawLaTeXInline
1018902
1019903rawHtmlInline' = do
1020904 st <- getState
1021− result <- if stateStrict st
1022− then choice [htmlBlockElement, anyHtmlTag, anyHtmlEndTag]
1023− else choice [htmlBlockElement, anyHtmlInlineTag]
1024− return (HtmlInline result)
905+ result <- choice $ if stateStrict st
906+ then [htmlBlockElement, anyHtmlTag, anyHtmlEndTag]
907+ else [htmlBlockElement, anyHtmlInlineTag]
908+ return $ HtmlInline result
1025909
src/Text/Pandoc/Readers/RST.hs+147−174
@@ -31,23 +31,14 @@ module Text.Pandoc.Readers.RST (
3131 readRST
3232 ) where
3333import Text.Pandoc.Definition
34−import Text.Pandoc.ParserCombinators
3534import Text.Pandoc.Shared
36−import Text.Pandoc.Readers.HTML ( anyHtmlBlockTag, anyHtmlInlineTag )
37−import Text.Regex ( matchRegex, mkRegex )
3835import Text.ParserCombinators.Parsec
39−import Data.Maybe ( fromMaybe )
4036import Data.List ( findIndex, delete )
41−import Data.Char ( toUpper )
4237
4338-- | Parse reStructuredText string and return Pandoc document.
4439readRST :: ParserState -> String -> Pandoc
4540readRST state str = (readWith parseRST) state (str ++ "\n\n")
4641
47−-- | Parse a string and print result (for testing).
48−testString :: String -> IO ()
49−testString = testStringWith parseRST
50−
5142--
5243-- Constants and data structure definitions
5344---
@@ -62,15 +53,11 @@ specialChars = "\\`|*_<>$:[-"
6253-- parsing documents
6354--
6455
65−isAnonKey (ref, src) = (ref == [Str "_"])
66−
67−isHeader1 :: Block -> Bool
68−isHeader1 (Header 1 _) = True
69−isHeader1 _ = False
56+isAnonKey (ref, src) = ref == [Str "_"]
7057
71−isHeader2 :: Block -> Bool
72−isHeader2 (Header 2 _) = True
73−isHeader2 _ = False
58+isHeader :: Int -> Block -> Bool
59+isHeader n (Header x _) = x == n
60+isHeader _ _ = False
7461
7562-- | Promote all headers in a list of blocks. (Part of
7663-- title transformation for RST.)
@@ -86,23 +73,23 @@ promoteHeaders num [] = []
8673titleTransform :: [Block] -- ^ list of blocks
8774 -> ([Block], [Inline]) -- ^ modified list of blocks, title
8875titleTransform ((Header 1 head1):(Header 2 head2):rest) = -- title subtitle
89− if (any isHeader1 rest) || (any isHeader2 rest)
76+ if (any (isHeader 1) rest) || (any (isHeader 2) rest)
9077 then ((Header 1 head1):(Header 2 head2):rest, [])
9178 else ((promoteHeaders 2 rest), head1 ++ [Str ":", Space] ++ head2)
9279titleTransform ((Header 1 head1):rest) = -- title, no subtitle
93− if (any isHeader1 rest)
80+ if (any (isHeader 1) rest)
9481 then ((Header 1 head1):rest, [])
9582 else ((promoteHeaders 1 rest), head1)
9683titleTransform blocks = (blocks, [])
9784
9885parseRST = do
99− -- first pass: get anonymous keys
100− refs <- manyTill (referenceKey <|> (do l <- lineClump
101− return (LineClump l))) eof
86+ -- first pass: get keys
87+ refs <- manyTill (referenceKey <|> (lineClump >>= return . LineClump)) eof
10288 let keys = map (\(KeyBlock label target) -> (label, target)) $
10389 filter isKeyBlock refs
90+ -- second pass, with keys stripped out
10491 let rawlines = map (\(LineClump ln) -> ln) $ filter isLineClump refs
105− setInput $ concat rawlines -- with keys stripped out
92+ setInput $ concat rawlines
10693 updateState (\state -> state { stateKeys = keys })
10794 blocks <- parseBlocks
10895 let blocks' = filter (/= Null) blocks
@@ -113,7 +100,7 @@ parseRST = do
113100 let authors = stateAuthors state
114101 let date = stateDate state
115102 let title' = if (null title) then (stateTitle state) else title
116− return (Pandoc (Meta title' authors date) blocks'')
103+ return $ Pandoc (Meta title' authors date) blocks''
117104
118105--
119106-- parsing blocks
@@ -121,32 +108,39 @@ parseRST = do
121108
122109parseBlocks = manyTill block eof
123110
124−block = choice [ codeBlock, rawHtmlBlock, rawLaTeXBlock, blockQuote,
125− imageBlock, unknownDirective, header,
126− hrule, list, fieldList, lineBlock, para, plain,
127− nullBlock ] <?> "block"
111+block = choice [ codeBlock
112+ , rawHtmlBlock
113+ , rawLaTeXBlock
114+ , blockQuote
115+ , imageBlock
116+ , unknownDirective
117+ , header
118+ , hrule
119+ , list
120+ , fieldList
121+ , lineBlock
122+ , para
123+ , plain
124+ , nullBlock ] <?> "block"
128125
129126--
130127-- field list
131128--
132129
133−fieldListItem = try (do
130+fieldListItem = try $ do
134131 char ':'
135132 name <- many1 alphaNum
136133 string ": "
137134 skipSpaces
138135 first <- manyTill anyChar newline
139− rest <- many (do
140− notFollowedBy (char ':')
141− notFollowedBy blankline
142− skipSpaces
143− manyTill anyChar newline )
144− return (name, (joinWithSep " " (first:rest))))
145−
146−fieldList = try (do
136+ rest <- many (notFollowedBy ((char ':') <|> blankline) >>
137+ skipSpaces >> manyTill anyChar newline)
138+ return $ (name, (joinWithSep " " (first:rest)))
139+
140+fieldList = try $ do
147141 items <- many1 fieldListItem
148142 blanklines
149− let authors = case (lookup "Authors" items) of
143+ let authors = case lookup "Authors" items of
150144 Just auth -> [auth]
151145 Nothing -> map snd (filter (\(x,y) -> x == "Author") items)
152146 let date = case (lookup "Date" items) of
@@ -162,82 +156,74 @@ fieldList = try (do
162156 updateState (\st -> st { stateAuthors = authors,
163157 stateDate = date,
164158 stateTitle = title })
165− return (BlockQuote result))
159+ return $ BlockQuote result
166160
167161--
168162-- line block
169163--
170164
171−lineBlockLine = try (do
165+lineBlockLine = try $ do
172166 string "| "
173167 white <- many (oneOf " \t")
174168 line <- manyTill inline newline
175− let line' = (if null white then [] else [Str white]) ++ line ++ [LineBreak]
176− return line')
169+ return $ (if null white then [] else [Str white]) ++ line ++ [LineBreak]
177170
178−lineBlock = try (do
171+lineBlock = try $ do
179172 lines <- many1 lineBlockLine
180173 blanklines
181− return $ Para (concat lines))
174+ return $ Para (concat lines)
182175
183176--
184177-- paragraph block
185178--
186179
187−para = choice [ paraBeforeCodeBlock, paraNormal ] <?> "paragraph"
180+para = paraBeforeCodeBlock <|> paraNormal <?> "paragraph"
188181
189−codeBlockStart = try (do
190− string "::"
191− blankline
192− blankline)
182+codeBlockStart = try $ string "::" >> blankline >> blankline
193183
194184-- paragraph that ends in a :: starting a code block
195−paraBeforeCodeBlock = try (do
196− result <- many1 (do {notFollowedBy' codeBlockStart; inline})
185+paraBeforeCodeBlock = try $ do
186+ result <- many1 (notFollowedBy' codeBlockStart >> inline)
197187 lookAhead (string "::")
198− return (Para (if (last result == Space)
199− then normalizeSpaces result
200− else (normalizeSpaces result) ++ [Str ":"])))
188+ return $ Para $ if last result == Space
189+ then normalizeSpaces result
190+ else (normalizeSpaces result) ++ [Str ":"]
201191
202192-- regular paragraph
203−paraNormal = try (do
193+paraNormal = try $ do
204194 result <- many1 inline
205195 newline
206196 blanklines
207− let result' = normalizeSpaces result
208− return (Para result'))
197+ return $ Para $ normalizeSpaces result
209198
210−plain = do
211− result <- many1 inline
212− let result' = normalizeSpaces result
213− return (Plain result')
199+plain = many1 inline >>= return . Plain . normalizeSpaces
214200
215201--
216202-- image block
217203--
218204
219−imageBlock = try (do
205+imageBlock = try $ do
220206 string ".. image:: "
221207 src <- manyTill anyChar newline
222− return (Plain [Image [Str "image"] (src, "")]))
208+ return $ Plain [Image [Str "image"] (src, "")]
223209
224210--
225211-- header blocks
226212--
227213
228−header = choice [ doubleHeader, singleHeader ] <?> "header"
214+header = doubleHeader <|> singleHeader <?> "header"
229215
230216-- a header with lines on top and bottom
231−doubleHeader = try (do
217+doubleHeader = try $ do
232218 c <- oneOf underlineChars
233219 rest <- many (char c) -- the top line
234220 let lenTop = length (c:rest)
235221 skipSpaces
236222 newline
237− txt <- many1 (do {notFollowedBy blankline; inline})
238− pos <- getPosition
223+ txt <- many1 (notFollowedBy blankline >> inline)
224+ pos <- getPosition
239225 let len = (sourceColumn pos) - 1
240− if (len > lenTop) then fail "title longer than border" else (do {return ()})
226+ if (len > lenTop) then fail "title longer than border" else return ()
241227 blankline -- spaces and newline
242228 count lenTop (char c) -- the bottom line
243229 blanklines
@@ -249,10 +235,10 @@ doubleHeader = try (do
249235 Just ind -> (headerTable, ind + 1)
250236 Nothing -> (headerTable ++ [DoubleHeader c], (length headerTable) + 1)
251237 setState (state { stateHeaderTable = headerTable' })
252− return (Header level (normalizeSpaces txt)))
238+ return $ Header level (normalizeSpaces txt)
253239
254240-- a header with line on the bottom only
255−singleHeader = try (do
241+singleHeader = try $ do
256242 notFollowedBy' whitespace
257243 txt <- many1 (do {notFollowedBy blankline; inline})
258244 pos <- getPosition
@@ -268,19 +254,19 @@ singleHeader = try (do
268254 Just ind -> (headerTable, ind + 1)
269255 Nothing -> (headerTable ++ [SingleHeader c], (length headerTable) + 1)
270256 setState (state { stateHeaderTable = headerTable' })
271− return (Header level (normalizeSpaces txt)))
257+ return $ Header level (normalizeSpaces txt)
272258
273259--
274260-- hrule block
275261--
276262
277−hruleWith chr = try (do
263+hruleWith chr = try $ do
278264 count 4 (char chr)
279265 skipMany (char chr)
280266 skipSpaces
281267 newline
282268 blanklines
283− return HorizontalRule)
269+ return HorizontalRule
284270
285271hrule = choice (map hruleWith underlineChars) <?> "hrule"
286272
@@ -289,15 +275,16 @@ hrule = choice (map hruleWith underlineChars) <?> "hrule"
289275--
290276
291277-- read a line indented by a given string
292−indentedLine indents = try (do
278+indentedLine indents = try $ do
293279 string indents
294280 result <- manyTill anyChar newline
295− return (result ++ "\n"))
281+ return $ result ++ "\n"
296282
297−-- two or more indented lines, possibly separated by blank lines
298−-- if variable = True, then any indent will work, but it must be consistent through the block
299−-- if variable = False, indent should be one tab or equivalent in spaces
300−indentedBlock variable = try (do
283+-- two or more indented lines, possibly separated by blank lines.
284+-- if variable = True, then any indent will work, but it must be
285+-- consistent through the block.
286+-- if variable = False, indent should be one tab or equivalent in spaces.
287+indentedBlock variable = try $ do
301288 state <- getState
302289 let tabStop = stateTabStop state
303290 indents <- if variable
@@ -305,51 +292,47 @@ indentedBlock variable = try (do
305292 else oneOfStrings ["\t", (replicate tabStop ' ')]
306293 firstline <- manyTill anyChar newline
307294 rest <- many (choice [ indentedLine indents,
308− try (do
309− b <- blanklines
310− l <- indentedLine indents
311− return (b ++ l))])
312− option "" blanklines
313− return (firstline ++ "\n" ++ (concat rest)))
314−
315−codeBlock = try (do
295+ try (do b <- blanklines
296+ l <- indentedLine indents
297+ return (b ++ l))])
298+ optional blanklines
299+ return $ firstline ++ "\n" ++ concat rest
300+
301+codeBlock = try $ do
316302 codeBlockStart
317303 result <- indentedBlock False
318304 -- the False means we want one tab stop indent on each line
319− return (CodeBlock (stripTrailingNewlines result)))
305+ return $ CodeBlock $ stripTrailingNewlines result
320306
321307--
322308-- raw html
323309--
324310
325−rawHtmlBlock = try (do
326− string ".. raw:: html"
327− blanklines
328− result <- indentedBlock True
329− return (RawHtml result))
311+rawHtmlBlock = try $ string ".. raw:: html" >> blanklines >>
312+ indentedBlock True >>= return . RawHtml
330313
331314--
332315-- raw latex
333316--
334317
335−rawLaTeXBlock = try (do
318+rawLaTeXBlock = try $ do
336319 string ".. raw:: latex"
337320 blanklines
338321 result <- indentedBlock True
339− return (Para [(TeX result)]))
322+ return $ Para [(TeX result)]
340323
341324--
342325-- block quotes
343326--
344327
345−blockQuote = try (do
328+blockQuote = try $ do
346329 raw <- indentedBlock True
347330 -- parse the extracted block, which may contain various block elements:
348331 rest <- getInput
349332 setInput $ raw ++ "\n\n"
350333 contents <- parseBlocks
351334 setInput rest
352− return (BlockQuote contents))
335+ return $ BlockQuote contents
353336
354337--
355338-- list blocks
@@ -369,15 +352,14 @@ definitionListItem = try $ do
369352
370353definitionList = try $ do
371354 items <- many1 definitionListItem
372− return (DefinitionList items)
355+ return $ DefinitionList items
373356
374357-- parses bullet list start and returns its length (inc. following whitespace)
375−bulletListStart = try (do
358+bulletListStart = try $ do
376359 notFollowedBy' hrule -- because hrules start out just like lists
377360 marker <- oneOf bulletListMarkers
378361 white <- many1 spaceChar
379− let len = length (marker:white)
380− return len)
362+ return $ length (marker:white)
381363
382364-- parses ordered list start and returns its length (inc following whitespace)
383365orderedListStart style delim = try $ do
@@ -386,11 +368,11 @@ orderedListStart style delim = try $ do
386368 return $ markerLen + length white
387369
388370-- parse a line of a list item
389−listLine markerLength = try (do
371+listLine markerLength = try $ do
390372 notFollowedBy blankline
391373 indentWith markerLength
392374 line <- manyTill anyChar newline
393− return (line ++ "\n"))
375+ return $ line ++ "\n"
394376
395377-- indent by specified number of spaces (or equiv. tabs)
396378indentWith num = do
@@ -399,7 +381,7 @@ indentWith num = do
399381 if (num < tabStop)
400382 then count num (char ' ')
401383 else choice [ try (count num (char ' ')),
402− (try (do {char '\t'; count (num - tabStop) (char ' ')})) ]
384+ (try (char '\t' >> count (num - tabStop) (char ' '))) ]
403385
404386-- parse raw text for one list item, excluding start marker and continuations
405387rawListItem start = try $ do
@@ -411,19 +393,16 @@ rawListItem start = try $ do
411393-- continuation of a list item - indented and separated by blankline or
412394-- (in compact lists) endline.
413395-- Note: nested lists are parsed as continuations.
414−listContinuation markerLength = try (do
396+listContinuation markerLength = try $ do
415397 blanks <- many1 blankline
416398 result <- many1 (listLine markerLength)
417− return (blanks ++ (concat result)))
399+ return $ blanks ++ concat result
418400
419−listItem start = try (do
401+listItem start = try $ do
420402 (markerLength, first) <- rawListItem start
421403 rest <- many (listContinuation markerLength)
422− blanks <- choice [ try (do
423− b <- many blankline
424− lookAhead start
425− return b),
426− many1 blankline ] -- whole list must end with blank
404+ blanks <- choice [ try (many blankline >>~ lookAhead start),
405+ many1 blankline ] -- whole list must end with blank.
427406 -- parsing with ListItemState forces markers at beginning of lines to
428407 -- count as list item markers, even if not separated by blank space.
429408 -- see definition of "endline"
@@ -436,52 +415,44 @@ listItem start = try (do
436415 parsed <- parseBlocks
437416 setInput remaining
438417 updateState (\st -> st {stateParserContext = oldContext})
439− return parsed)
418+ return parsed
440419
441420orderedList = try $ do
442421 (start, style, delim) <- lookAhead anyOrderedListMarker
443422 items <- many1 (listItem (orderedListStart style delim))
444423 let items' = compactify items
445− return (OrderedList (start, style, delim) items')
424+ return $ OrderedList (start, style, delim) items'
446425
447−bulletList = try (do
426+bulletList = try $ do
448427 items <- many1 (listItem bulletListStart)
449428 let items' = compactify items
450− return (BulletList items'))
429+ return $ BulletList items'
451430
452431--
453432-- unknown directive (e.g. comment)
454433--
455434
456−unknownDirective = try (do
435+unknownDirective = try $ do
457436 string ".. "
458437 manyTill anyChar newline
459− many (do
460− string " "
461− char ':'
462− many1 (noneOf "\n:")
463− char ':'
464− many1 (noneOf "\n")
465− newline)
466− option "" blanklines
467− return Null)
438+ many (string " :" >> many1 (noneOf "\n:") >> char ':' >>
439+ many1 (noneOf "\n") >> newline)
440+ optional blanklines
441+ return Null
468442
469443--
470444-- reference key
471445--
472446
473−referenceKey = do
474− result <- choice [imageKey, anonymousKey, regularKeyQuoted, regularKey]
475− option "" blanklines
476− return result
447+referenceKey =
448+ choice [imageKey, anonymousKey, regularKeyQuoted, regularKey] >>~
449+ optional blanklines
477450
478451targetURI = try $ do
479452 skipSpaces
480− option ' ' newline
481− contents <- many1 (try (do many spaceChar
482− newline
483− many1 spaceChar
484− noneOf " \t\n") <|> noneOf "\n")
453+ optional newline
454+ contents <- many1 (try (many spaceChar >> newline >>
455+ many1 spaceChar >> noneOf " \t\n") <|> noneOf "\n")
485456 blanklines
486457 return contents
487458
@@ -516,71 +487,73 @@ regularKey = try $ do
516487 -- inline
517488 --
518489
519−inline = choice [ superscript, subscript,
520− escapedChar, link, image, hyphens, strong, emph, code,
521− str, tabchar, whitespace, endline, symbol ] <?> "inline"
522−
523−hyphens = try (do
490+inline = choice [ superscript
491+ , subscript
492+ , escapedChar
493+ , link
494+ , image
495+ , hyphens
496+ , strong
497+ , emph
498+ , code
499+ , str
500+ , tabchar
501+ , whitespace
502+ , endline
503+ , symbol ] <?> "inline"
504+
505+hyphens = try $ do
524506 result <- many1 (char '-')
525507 option Space endline
526508 -- don't want to treat endline after hyphen or dash as a space
527− return (Str result))
509+ return $ Str result
528510
529511escapedChar = escaped anyChar
530512
531513symbol = do
532514 result <- oneOf specialChars
533− return (Str [result])
515+ return $ Str [result]
534516
535517-- parses inline code, between codeStart and codeEnd
536−code = try (do
518+code = try $ do
537519 string "``"
538520 result <- manyTill anyChar (try (string "``"))
539− let result' = removeLeadingTrailingSpace $ joinWithSep " " $ lines result
540− return (Code result'))
521+ return $ Code $ removeLeadingTrailingSpace $ joinWithSep " " $ lines result
541522
542−emph = do
543− result <- enclosed (char '*') (char '*') inline
544− return (Emph (normalizeSpaces result))
523+emph = enclosed (char '*') (char '*') inline >>=
524+ return . Emph . normalizeSpaces
545525
546−strong = do
547− result <- enclosed (string "**") (string "**") inline
548− return (Strong (normalizeSpaces result))
526+strong = enclosed (string "**") (string "**") inline >>=
527+ return . Strong . normalizeSpaces
549528
550529interpreted role = try $ do
551− option "" (try $ string "\\ ")
530+ optional $ try $ string "\\ "
552531 result <- enclosed (string $ ":" ++ role ++ ":`") (char '`') anyChar
553532 nextChar <- lookAhead anyChar
554533 try (string "\\ ") <|> lookAhead (count 1 $ oneOf " \t\n") <|> (eof >> return "")
555534 return [Str result]
556535
557536superscript = interpreted "sup" >>= (return . Superscript)
537+
558538subscript = interpreted "sub" >>= (return . Subscript)
559539
560−whitespace = do
561− many1 spaceChar <?> "whitespace"
562− return Space
540+whitespace = many1 spaceChar >> return Space <?> "whitespace"
563541
564−tabchar = do
565− tab
566− return (Str "\t")
542+tabchar = tab >> return (Str "\t")
567543
568−str = do
569− notFollowedBy' oneWordReference
570− result <- many1 (noneOf (specialChars ++ "\t\n "))
571− return (Str result)
544+str = notFollowedBy' oneWordReference >>
545+ many1 (noneOf (specialChars ++ "\t\n ")) >>= return . Str
572546
573547-- an endline character that can be treated as a space, not a structural break
574−endline = try (do
548+endline = try $ do
575549 newline
576550 notFollowedBy blankline
577551 -- parse potential list-starts at beginning of line differently in a list:
578552 st <- getState
579553 if ((stateParserContext st) == ListItemState)
580− then do notFollowedBy' anyOrderedListMarker
581− notFollowedBy' bulletListStart
582− else option () pzero
583− return Space)
554+ then notFollowedBy' anyOrderedListMarker >> notFollowedBy' bulletListStart
555+ else return ()
556+ return Space
584557
585558--
586559-- links
@@ -628,10 +601,10 @@ referenceLink = try $ do
628601uriScheme = oneOfStrings [ "http://", "https://", "ftp://", "file://",
629602 "mailto:", "news:", "telnet:" ]
630603
631−uri = try (do
604+uri = try $ do
632605 scheme <- uriScheme
633606 identifier <- many1 (noneOf " \t\n")
634− return (scheme ++ identifier))
607+ return $ scheme ++ identifier
635608
636609autoURI = try $ do
637610 src <- uri
@@ -639,20 +612,20 @@ autoURI = try $ do
639612
640613emailChar = alphaNum <|> oneOf "-+_."
641614
642−emailAddress = try (do
615+emailAddress = try $ do
643616 firstLetter <- alphaNum
644617 restAddr <- many emailChar
645618 let addr = firstLetter:restAddr
646619 char '@'
647620 dom <- domain
648− return (addr ++ '@':dom))
621+ return $ addr ++ '@':dom
649622
650623domainChar = alphaNum <|> char '-'
651624
652−domain = try (do
625+domain = try $ do
653626 first <- many1 domainChar
654627 dom <- many1 (try (do{ char '.'; many1 domainChar }))
655− return (joinWithSep "." (first:dom)))
628+ return $ joinWithSep "." (first:dom)
656629
657630autoEmail = try $ do
658631 src <- emailAddress
@@ -669,5 +642,5 @@ image = try $ do
669642 src <- case lookupKeySrc keyTable ref of
670643 Nothing -> fail "no corresponding key"
671644 Just target -> return target
672− return (Image (normalizeSpaces ref) src)
645+ return $ Image (normalizeSpaces ref) src
673646
src/Text/Pandoc/Shared.hs+576−404
@@ -32,12 +32,11 @@ module Text.Pandoc.Shared (
3232 splitBy,
3333 splitByIndices,
3434 substitute,
35− -- * Text processing
3635 joinWithSep,
36+ -- * Text processing
3737 tabsToSpaces,
3838 backslashEscapes,
3939 escapeStringUsing,
40− endsWith,
4140 stripTrailingNewlines,
4241 removeLeadingTrailingSpace,
4342 removeLeadingSpace,
@@ -46,8 +45,33 @@ module Text.Pandoc.Shared (
4645 camelCaseToHyphenated,
4746 toRomanNumeral,
4847 -- * Parsing
48+ (>>~),
49+ anyLine,
50+ many1Till,
51+ notFollowedBy',
52+ oneOfStrings,
53+ spaceChar,
54+ skipSpaces,
55+ blankline,
56+ blanklines,
57+ enclosed,
58+ stringAnyCase,
59+ parseFromString,
60+ lineClump,
61+ charsInBalanced,
62+ charsInBalanced',
63+ romanNumeral,
64+ withHorizDisplacement,
65+ nullBlock,
66+ failIfStrict,
67+ escaped,
68+ anyOrderedListMarker,
69+ orderedListMarker,
70+ charRef,
4971 readWith,
5072 testStringWith,
73+ ParserState (..),
74+ defaultParserState,
5175 Reference (..),
5276 isNoteBlock,
5377 isKeyBlock,
@@ -55,14 +79,10 @@ module Text.Pandoc.Shared (
5579 HeaderType (..),
5680 ParserContext (..),
5781 QuoteContext (..),
58− ParserState (..),
5982 NoteTable,
60− defaultParserState,
61− nullBlock,
62− failIfStrict,
63− escaped,
64− anyOrderedListMarker,
65− orderedListMarker,
83+ KeyTable,
84+ lookupKeySrc,
85+ refsMatch,
6686 -- * Native format prettyprinting
6787 prettyPandoc,
6888 -- * Pandoc block and inline list processing
@@ -74,214 +94,387 @@ module Text.Pandoc.Shared (
7494 isHeaderBlock,
7595 -- * Writer options
7696 WriterOptions (..),
77− defaultWriterOptions,
78− -- * Reference key lookup functions
79− KeyTable,
80− lookupKeySrc,
81− refsMatch,
97+ defaultWriterOptions
8298 ) where
99+
83100import Text.Pandoc.Definition
84101import Text.ParserCombinators.Parsec
85−import Text.Pandoc.ParserCombinators
86−import Text.Pandoc.Entities ( decodeEntities, escapeStringForXML )
102+import Text.Pandoc.CharacterReferences ( characterReference )
87103import Data.Char ( toLower, toUpper, ord, chr, isLower, isUpper )
88−import Data.List ( find, groupBy, isPrefixOf )
104+import Data.List ( find, groupBy, isPrefixOf, isSuffixOf )
89105
90−-- | Parse a string with a given parser and state.
91−readWith :: GenParser Char ParserState a -- ^ parser
92− -> ParserState -- ^ initial state
93− -> String -- ^ input string
94− -> a
95−readWith parser state input =
96− case runParser parser state "source" input of
97− Left err -> error $ "\nError:\n" ++ show err
98− Right result -> result
106+--
107+-- List processing
108+--
99109
100−-- | Parse a string with @parser@ (for testing).
101−testStringWith :: (Show a) =>
102− GenParser Char ParserState a
103− -> String
104− -> IO ()
105−testStringWith parser str = putStrLn $ show $
106− readWith parser defaultParserState str
110+-- | Split list by groups of one or more sep.
111+splitBy :: (Eq a) => a -> [a] -> [[a]]
112+splitBy _ [] = []
113+splitBy sep lst =
114+ let (first, rest) = break (== sep) lst
115+ rest' = dropWhile (== sep) rest
116+ in first:(splitBy sep rest')
107117
108−data HeaderType
109− = SingleHeader Char -- ^ Single line of characters underneath
110− | DoubleHeader Char -- ^ Lines of characters above and below
111− deriving (Eq, Show)
118+-- | Split list into chunks divided at specified indices.
119+splitByIndices :: [Int] -> [a] -> [[a]]
120+splitByIndices [] lst = [lst]
121+splitByIndices (x:xs) lst =
122+ let (first, rest) = splitAt x lst in
123+ first:(splitByIndices (map (\y -> y - x) xs) rest)
112124
113−data ParserContext
114− = ListItemState -- ^ Used when running parser on list item contents
115− | NullState -- ^ Default state
116− deriving (Eq, Show)
125+-- | Replace each occurrence of one sublist in a list with another.
126+substitute :: (Eq a) => [a] -> [a] -> [a] -> [a]
127+substitute _ _ [] = []
128+substitute [] _ lst = lst
129+substitute target replacement lst =
130+ if target `isPrefixOf` lst
131+ then replacement ++ (substitute target replacement $ drop (length target) lst)
132+ else (head lst):(substitute target replacement $ tail lst)
117133
118−data QuoteContext
119− = InSingleQuote -- ^ Used when we're parsing inside single quotes
120− | InDoubleQuote -- ^ Used when we're parsing inside double quotes
121− | NoQuote -- ^ Used when we're not parsing inside quotes
122− deriving (Eq, Show)
134+-- | Joins a list of lists, separated by another list.
135+joinWithSep :: [a] -- ^ List to use as separator
136+ -> [[a]] -- ^ Lists to join
137+ -> [a]
138+joinWithSep sep [] = []
139+joinWithSep sep lst = foldr1 (\a b -> a ++ sep ++ b) lst
123140
124−type KeyTable = [([Inline], Target)]
141+--
142+-- Text processing
143+--
125144
126−type NoteTable = [(String, [Block])]
145+-- | Convert tabs to spaces (with adjustable tab stop).
146+tabsToSpaces :: Int -- ^ Tabstop
147+ -> String -- ^ String to convert
148+ -> String
149+tabsToSpaces tabstop str =
150+ unlines $ map (tabsInLine tabstop tabstop) (lines str)
127151
128−-- | References from preliminary parsing
129−data Reference
130− = KeyBlock [Inline] Target -- ^ Key for reference-style link (label URL title)
131− | NoteBlock String [Block] -- ^ Footnote reference and contents
132− | LineClump String -- ^ Raw clump of lines with blanks at end
133− deriving (Eq, Read, Show)
152+-- | Convert tabs to spaces in one line.
153+tabsInLine :: Int -- ^ Number of spaces to next tab stop
154+ -> Int -- ^ Tabstop
155+ -> String -- ^ Line to convert
156+ -> String
157+tabsInLine num tabstop [] = ""
158+tabsInLine num tabstop (c:cs) =
159+ let (replacement, nextnum) = if c == '\t'
160+ then (replicate num ' ', tabstop)
161+ else if num > 1
162+ then ([c], num - 1)
163+ else ([c], tabstop)
164+ in replacement ++ tabsInLine nextnum tabstop cs
134165
135−-- | Auxiliary functions used in preliminary parsing
136−isNoteBlock :: Reference -> Bool
137−isNoteBlock (NoteBlock _ _) = True
138−isNoteBlock _ = False
166+-- | Returns an association list of backslash escapes for the
167+-- designated characters.
168+backslashEscapes :: [Char] -- ^ list of special characters to escape
169+ -> [(Char, String)]
170+backslashEscapes = map (\ch -> (ch, ['\\',ch]))
139171
140−isKeyBlock :: Reference -> Bool
141−isKeyBlock (KeyBlock _ _) = True
142−isKeyBlock _ = False
172+-- | Escape a string of characters, using an association list of
173+-- characters and strings.
174+escapeStringUsing :: [(Char, String)] -> String -> String
175+escapeStringUsing escapeTable [] = ""
176+escapeStringUsing escapeTable (x:xs) =
177+ case (lookup x escapeTable) of
178+ Just str -> str ++ rest
179+ Nothing -> x:rest
180+ where rest = escapeStringUsing escapeTable xs
143181
144−isLineClump :: Reference -> Bool
145−isLineClump (LineClump _) = True
146−isLineClump _ = False
182+-- | Strip trailing newlines from string.
183+stripTrailingNewlines :: String -> String
184+stripTrailingNewlines = reverse . dropWhile (== '\n') . reverse
147185
148−data ParserState = ParserState
149− { stateParseRaw :: Bool, -- ^ Parse untranslatable HTML
150− -- and LaTeX?
151− stateParserContext :: ParserContext, -- ^ What are we parsing?
152− stateQuoteContext :: QuoteContext, -- ^ Inside quoted environment?
153− stateKeys :: KeyTable, -- ^ List of reference keys
154− stateNotes :: NoteTable, -- ^ List of notes
155− stateTabStop :: Int, -- ^ Tab stop
156− stateStandalone :: Bool, -- ^ If @True@, parse
157− -- bibliographic info
158− stateTitle :: [Inline], -- ^ Title of document
159− stateAuthors :: [String], -- ^ Authors of document
160− stateDate :: String, -- ^ Date of document
161− stateStrict :: Bool, -- ^ Use strict markdown syntax
162− stateSmart :: Bool, -- ^ Use smart typography
163− stateColumns :: Int, -- ^ Number of columns in
164− -- terminal (used for tables)
165− stateHeaderTable :: [HeaderType] -- ^ List of header types used,
166− -- in what order (rst only)
167− }
168− deriving Show
186+-- | Remove leading and trailing space (including newlines) from string.
187+removeLeadingTrailingSpace :: String -> String
188+removeLeadingTrailingSpace = removeLeadingSpace . removeTrailingSpace
169189
170−defaultParserState :: ParserState
171−defaultParserState =
172− ParserState { stateParseRaw = False,
173− stateParserContext = NullState,
174− stateQuoteContext = NoQuote,
175− stateKeys = [],
176− stateNotes = [],
177− stateTabStop = 4,
178− stateStandalone = False,
179− stateTitle = [],
180− stateAuthors = [],
181− stateDate = [],
182− stateStrict = False,
183− stateSmart = False,
184− stateColumns = 80,
185− stateHeaderTable = [] }
190+-- | Remove leading space (including newlines) from string.
191+removeLeadingSpace :: String -> String
192+removeLeadingSpace = dropWhile (`elem` " \n\t")
193+
194+-- | Remove trailing space (including newlines) from string.
195+removeTrailingSpace :: String -> String
196+removeTrailingSpace = reverse . removeLeadingSpace . reverse
197+
198+-- | Strip leading and trailing characters from string
199+stripFirstAndLast :: String -> String
200+stripFirstAndLast str =
201+ drop 1 $ take ((length str) - 1) str
202+
203+-- | Change CamelCase word to hyphenated lowercase (e.g., camel-case).
204+camelCaseToHyphenated :: String -> String
205+camelCaseToHyphenated [] = ""
206+camelCaseToHyphenated (a:b:rest) | isLower a && isUpper b =
207+ a:'-':(toLower b):(camelCaseToHyphenated rest)
208+camelCaseToHyphenated (a:rest) = (toLower a):(camelCaseToHyphenated rest)
209+
210+-- | Convert number < 4000 to uppercase roman numeral.
211+toRomanNumeral :: Int -> String
212+toRomanNumeral x =
213+ if x >= 4000 || x < 0
214+ then "?"
215+ else case x of
216+ x | x >= 1000 -> "M" ++ toRomanNumeral (x - 1000)
217+ x | x >= 900 -> "CM" ++ toRomanNumeral (x - 900)
218+ x | x >= 500 -> "D" ++ toRomanNumeral (x - 500)
219+ x | x >= 400 -> "CD" ++ toRomanNumeral (x - 400)
220+ x | x >= 100 -> "C" ++ toRomanNumeral (x - 100)
221+ x | x >= 90 -> "XC" ++ toRomanNumeral (x - 90)
222+ x | x >= 50 -> "L" ++ toRomanNumeral (x - 50)
223+ x | x >= 40 -> "XL" ++ toRomanNumeral (x - 40)
224+ x | x >= 10 -> "X" ++ toRomanNumeral (x - 10)
225+ x | x >= 9 -> "IX" ++ toRomanNumeral (x - 5)
226+ x | x >= 5 -> "V" ++ toRomanNumeral (x - 5)
227+ x | x >= 4 -> "IV" ++ toRomanNumeral (x - 4)
228+ x | x >= 1 -> "I" ++ toRomanNumeral (x - 1)
229+ 0 -> ""
230+
231+--
232+-- Parsing
233+--
234+
235+-- | Like >>, but returns the operation on the left.
236+-- (Suggested by Tillmann Rendel on Haskell-cafe list.)
237+(>>~) :: (Monad m) => m a -> m b -> m a
238+a >>~ b = a >>= \x -> b >> return x
239+
240+-- | Parse any line of text
241+anyLine :: GenParser Char st [Char]
242+anyLine = try (manyTill anyChar newline) <|> many1 anyChar
243+ -- second alternative is for a line ending with eof
244+
245+-- | Like @manyTill@, but reads at least one item.
246+many1Till :: GenParser tok st a
247+ -> GenParser tok st end
248+ -> GenParser tok st [a]
249+many1Till p end = do
250+ first <- p
251+ rest <- manyTill p end
252+ return (first:rest)
253+
254+-- | A more general form of @notFollowedBy@. This one allows any
255+-- type of parser to be specified, and succeeds only if that parser fails.
256+-- It does not consume any input.
257+notFollowedBy' :: Show b => GenParser a st b -> GenParser a st ()
258+notFollowedBy' parser = try $ (do result <- try parser
259+ unexpected (show result))
260+ <|> return ()
261+
262+-- | Parses one of a list of strings (tried in order).
263+oneOfStrings :: [String] -> GenParser Char st String
264+oneOfStrings listOfStrings = choice $ map (try . string) listOfStrings
265+
266+-- | Parses a space or tab.
267+spaceChar :: CharParser st Char
268+spaceChar = oneOf " \t"
269+
270+-- | Skips zero or more spaces or tabs.
271+skipSpaces :: GenParser Char st ()
272+skipSpaces = skipMany spaceChar
273+
274+-- | Skips zero or more spaces or tabs, then reads a newline.
275+blankline :: GenParser Char st Char
276+blankline = try $ skipSpaces >> newline
277+
278+-- | Parses one or more blank lines and returns a string of newlines.
279+blanklines :: GenParser Char st [Char]
280+blanklines = many1 blankline
281+
282+-- | Parses material enclosed between start and end parsers.
283+enclosed :: GenParser Char st t -- ^ start parser
284+ -> GenParser Char st end -- ^ end parser
285+ -> GenParser Char st a -- ^ content parser (to be used repeatedly)
286+ -> GenParser Char st [a]
287+enclosed start end parser = try $
288+ start >> notFollowedBy space >> many1Till parser (try end)
289+
290+-- | Parse string, case insensitive.
291+stringAnyCase :: [Char] -> CharParser st String
292+stringAnyCase [] = string ""
293+stringAnyCase (x:xs) = try $ do
294+ firstChar <- choice [ char (toUpper x), char (toLower x) ]
295+ rest <- stringAnyCase xs
296+ return (firstChar:rest)
297+
298+-- | Parse contents of 'str' using 'parser' and return result.
299+parseFromString :: GenParser tok st a -> [tok] -> GenParser tok st a
300+parseFromString parser str = try $ do
301+ oldInput <- getInput
302+ setInput str
303+ result <- parser
304+ setInput oldInput
305+ return result
306+
307+-- | Parse raw line block up to and including blank lines.
308+lineClump :: GenParser Char st String
309+lineClump = do
310+ lines <- many1 (notFollowedBy blankline >> anyLine)
311+ blanks <- blanklines <|> (eof >> return "\n")
312+ return $ (unlines lines) ++ blanks
313+
314+-- | Parse a string of characters between an open character
315+-- and a close character, including text between balanced
316+-- pairs of open and close. For example,
317+-- @charsInBalanced '(' ')'@ will parse "(hello (there))"
318+-- and return "hello (there)". Stop if a blank line is
319+-- encountered.
320+charsInBalanced :: Char -> Char -> GenParser Char st String
321+charsInBalanced open close = try $ do
322+ char open
323+ raw <- manyTill ( (do res <- charsInBalanced open close
324+ return $ [open] ++ res ++ [close])
325+ <|> (do notFollowedBy' (blankline >> blanklines)
326+ count 1 anyChar))
327+ (char close)
328+ return $ concat raw
329+
330+-- | Like @charsInBalanced@, but allow blank lines in the content.
331+charsInBalanced' :: Char -> Char -> GenParser Char st String
332+charsInBalanced' open close = try $ do
333+ char open
334+ raw <- manyTill ( (do res <- charsInBalanced open close
335+ return $ [open] ++ res ++ [close])
336+ <|> count 1 anyChar)
337+ (char close)
338+ return $ concat raw
339+
340+-- | Parses a roman numeral (uppercase or lowercase), returns number.
341+romanNumeral :: Bool -- ^ Uppercase if true
342+ -> GenParser Char st Int
343+romanNumeral upper = try $ do
344+ let charAnyCase c = char (if upper then toUpper c else c)
345+ let one = charAnyCase 'i'
346+ let five = charAnyCase 'v'
347+ let ten = charAnyCase 'x'
348+ let fifty = charAnyCase 'l'
349+ let hundred = charAnyCase 'c'
350+ let fivehundred = charAnyCase 'd'
351+ let thousand = charAnyCase 'm'
352+ thousands <- many thousand >>= (return . (1000 *) . length)
353+ ninehundreds <- option 0 $ try $ hundred >> thousand >> return 900
354+ fivehundreds <- many fivehundred >>= (return . (500 *) . length)
355+ fourhundreds <- option 0 $ try $ hundred >> fivehundred >> return 400
356+ hundreds <- many hundred >>= (return . (100 *) . length)
357+ nineties <- option 0 $ try $ ten >> hundred >> return 90
358+ fifties <- many fifty >>= (return . (50 *) . length)
359+ forties <- option 0 $ try $ ten >> fifty >> return 40
360+ tens <- many ten >>= (return . (10 *) . length)
361+ nines <- option 0 $ try $ one >> ten >> return 9
362+ fives <- many five >>= (return . (5 *) . length)
363+ fours <- option 0 $ try $ one >> five >> return 4
364+ ones <- many one >>= (return . length)
365+ let total = thousands + ninehundreds + fivehundreds + fourhundreds +
366+ hundreds + nineties + fifties + forties + tens + nines +
367+ fives + fours + ones
368+ if total == 0
369+ then fail "not a roman numeral"
370+ else return total
371+
372+-- | Applies a parser, returns tuple of its results and its horizontal
373+-- displacement (the difference between the source column at the end
374+-- and the source column at the beginning). Vertical displacement
375+-- (source row) is ignored.
376+withHorizDisplacement :: GenParser Char st a -- ^ Parser to apply
377+ -> GenParser Char st (a, Int) -- ^ (result, displacement)
378+withHorizDisplacement parser = do
379+ pos1 <- getPosition
380+ result <- parser
381+ pos2 <- getPosition
382+ return (result, sourceColumn pos2 - sourceColumn pos1)
186383
187384-- | Parses a character and returns 'Null' (so that the parser can move on
188385-- if it gets stuck).
189386nullBlock :: GenParser Char st Block
190−nullBlock = do
191− anyChar
192− return Null
387+nullBlock = anyChar >> return Null
193388
194389-- | Fail if reader is in strict markdown syntax mode.
195390failIfStrict :: GenParser Char ParserState ()
196391failIfStrict = do
197392 state <- getState
198− if stateStrict state then fail "Strict mode" else return ()
393+ if stateStrict state then fail "strict mode" else return ()
199394
200395-- | Parses backslash, then applies character parser.
201396escaped :: GenParser Char st Char -- ^ Parser for character to escape
202397 -> GenParser Char st Inline
203−escaped parser = try (do
204− char '\\'
205− result <- parser
206− return (Str [result]))
398+escaped parser = try $ do
399+ char '\\'
400+ result <- parser
401+ return (Str [result])
207402
208403-- | Parses an uppercase roman numeral and returns (UpperRoman, number).
209404upperRoman :: GenParser Char st (ListNumberStyle, Int)
210405upperRoman = do
211− num <- romanNumeral True
212− return (UpperRoman, num)
406+ num <- romanNumeral True
407+ return (UpperRoman, num)
213408
214409-- | Parses a lowercase roman numeral and returns (LowerRoman, number).
215410lowerRoman :: GenParser Char st (ListNumberStyle, Int)
216411lowerRoman = do
217− num <- romanNumeral False
218− return (LowerRoman, num)
412+ num <- romanNumeral False
413+ return (LowerRoman, num)
219414
220415-- | Parses a decimal numeral and returns (Decimal, number).
221416decimal :: GenParser Char st (ListNumberStyle, Int)
222417decimal = do
223− num <- many1 digit
224− return (Decimal, read num)
418+ num <- many1 digit
419+ return (Decimal, read num)
225420
226421-- | Parses a '#' returns (DefaultStyle, 1).
227422defaultNum :: GenParser Char st (ListNumberStyle, Int)
228423defaultNum = do
229− char '#'
230− return (DefaultStyle, 1)
424+ char '#'
425+ return (DefaultStyle, 1)
231426
232427-- | Parses a lowercase letter and returns (LowerAlpha, number).
233428lowerAlpha :: GenParser Char st (ListNumberStyle, Int)
234429lowerAlpha = do
235− ch <- oneOf ['a'..'z']
236− return (LowerAlpha, ord ch - ord 'a' + 1)
430+ ch <- oneOf ['a'..'z']
431+ return (LowerAlpha, ord ch - ord 'a' + 1)
237432
238433-- | Parses an uppercase letter and returns (UpperAlpha, number).
239434upperAlpha :: GenParser Char st (ListNumberStyle, Int)
240435upperAlpha = do
241− ch <- oneOf ['A'..'Z']
242− return (UpperAlpha, ord ch - ord 'A' + 1)
436+ ch <- oneOf ['A'..'Z']
437+ return (UpperAlpha, ord ch - ord 'A' + 1)
243438
244439-- | Parses a roman numeral i or I
245440romanOne :: GenParser Char st (ListNumberStyle, Int)
246−romanOne = (do char 'i'
247− return (LowerRoman, 1)) <|>
248− (do char 'I'
249− return (UpperRoman, 1))
441+romanOne = (char 'i' >> return (LowerRoman, 1)) <|>
442+ (char 'I' >> return (UpperRoman, 1))
250443
251444-- | Parses an ordered list marker and returns list attributes.
252445anyOrderedListMarker :: GenParser Char st ListAttributes
253−anyOrderedListMarker = choice $ [delimParser numParser | delimParser <-
254− [inPeriod, inOneParen, inTwoParens],
255− numParser <- [decimal, defaultNum, romanOne,
256− lowerAlpha, lowerRoman, upperAlpha, upperRoman]]
446+anyOrderedListMarker = choice $
447+ [delimParser numParser | delimParser <- [inPeriod, inOneParen, inTwoParens],
448+ numParser <- [decimal, defaultNum, romanOne,
449+ lowerAlpha, lowerRoman, upperAlpha, upperRoman]]
257450
258451-- | Parses a list number (num) followed by a period, returns list attributes.
259452inPeriod :: GenParser Char st (ListNumberStyle, Int)
260453 -> GenParser Char st ListAttributes
261454inPeriod num = try $ do
262− (style, start) <- num
263− char '.'
264− let delim = if style == DefaultStyle
265− then DefaultDelim
266− else Period
267− return (start, style, delim)
455+ (style, start) <- num
456+ char '.'
457+ let delim = if style == DefaultStyle
458+ then DefaultDelim
459+ else Period
460+ return (start, style, delim)
268461
269462-- | Parses a list number (num) followed by a paren, returns list attributes.
270463inOneParen :: GenParser Char st (ListNumberStyle, Int)
271464 -> GenParser Char st ListAttributes
272465inOneParen num = try $ do
273− (style, start) <- num
274− char ')'
275− return (start, style, OneParen)
466+ (style, start) <- num
467+ char ')'
468+ return (start, style, OneParen)
276469
277470-- | Parses a list number (num) enclosed in parens, returns list attributes.
278471inTwoParens :: GenParser Char st (ListNumberStyle, Int)
279472 -> GenParser Char st ListAttributes
280473inTwoParens num = try $ do
281− char '('
282− (style, start) <- num
283− char ')'
284− return (start, style, TwoParens)
474+ char '('
475+ (style, start) <- num
476+ char ')'
477+ return (start, style, TwoParens)
285478
286479-- | Parses an ordered list marker with a given style and delimiter,
287480-- returns number.
@@ -289,21 +482,158 @@ orderedListMarker :: ListNumberStyle
289482 -> ListNumberDelim
290483 -> GenParser Char st Int
291484orderedListMarker style delim = do
292− let num = case style of
293− DefaultStyle -> decimal <|> defaultNum
294− Decimal -> decimal
295− UpperRoman -> upperRoman
296− LowerRoman -> lowerRoman
297− UpperAlpha -> upperAlpha
298− LowerAlpha -> lowerAlpha
299− let context = case delim of
300− DefaultDelim -> inPeriod
301− Period -> inPeriod
302− OneParen -> inOneParen
303− TwoParens -> inTwoParens
304− (start, style, delim) <- context num
305− return start
485+ let num = case style of
486+ DefaultStyle -> decimal <|> defaultNum
487+ Decimal -> decimal
488+ UpperRoman -> upperRoman
489+ LowerRoman -> lowerRoman
490+ UpperAlpha -> upperAlpha
491+ LowerAlpha -> lowerAlpha
492+ let context = case delim of
493+ DefaultDelim -> inPeriod
494+ Period -> inPeriod
495+ OneParen -> inOneParen
496+ TwoParens -> inTwoParens
497+ (start, style, delim) <- context num
498+ return start
499+
500+-- | Parses a character reference and returns a Str element.
501+charRef :: GenParser Char st Inline
502+charRef = do
503+ c <- characterReference
504+ return $ Str [c]
505+
506+-- | Parse a string with a given parser and state.
507+readWith :: GenParser Char ParserState a -- ^ parser
508+ -> ParserState -- ^ initial state
509+ -> String -- ^ input string
510+ -> a
511+readWith parser state input =
512+ case runParser parser state "source" input of
513+ Left err -> error $ "\nError:\n" ++ show err
514+ Right result -> result
515+
516+-- | Parse a string with @parser@ (for testing).
517+testStringWith :: (Show a) => GenParser Char ParserState a
518+ -> String
519+ -> IO ()
520+testStringWith parser str = putStrLn $ show $
521+ readWith parser defaultParserState str
522+
523+-- | Parsing options.
524+data ParserState = ParserState
525+ { stateParseRaw :: Bool, -- ^ Parse raw HTML and LaTeX?
526+ stateParserContext :: ParserContext, -- ^ Inside list?
527+ stateQuoteContext :: QuoteContext, -- ^ Inside quoted environment?
528+ stateKeys :: KeyTable, -- ^ List of reference keys
529+ stateNotes :: NoteTable, -- ^ List of notes
530+ stateTabStop :: Int, -- ^ Tab stop
531+ stateStandalone :: Bool, -- ^ Parse bibliographic info?
532+ stateTitle :: [Inline], -- ^ Title of document
533+ stateAuthors :: [String], -- ^ Authors of document
534+ stateDate :: String, -- ^ Date of document
535+ stateStrict :: Bool, -- ^ Use strict markdown syntax?
536+ stateSmart :: Bool, -- ^ Use smart typography?
537+ stateColumns :: Int, -- ^ Number of columns in terminal
538+ stateHeaderTable :: [HeaderType] -- ^ Ordered list of header types used
539+ }
540+ deriving Show
541+
542+defaultParserState :: ParserState
543+defaultParserState =
544+ ParserState { stateParseRaw = False,
545+ stateParserContext = NullState,
546+ stateQuoteContext = NoQuote,
547+ stateKeys = [],
548+ stateNotes = [],
549+ stateTabStop = 4,
550+ stateStandalone = False,
551+ stateTitle = [],
552+ stateAuthors = [],
553+ stateDate = [],
554+ stateStrict = False,
555+ stateSmart = False,
556+ stateColumns = 80,
557+ stateHeaderTable = [] }
558+
559+-- | References from preliminary parsing.
560+data Reference
561+ = KeyBlock [Inline] Target -- ^ Key for reference-style link (label URL title)
562+ | NoteBlock String [Block] -- ^ Footnote reference and contents
563+ | LineClump String -- ^ Raw clump of lines with blanks at end
564+ deriving (Eq, Read, Show)
565+
566+-- | Auxiliary functions used in preliminary parsing.
567+isNoteBlock :: Reference -> Bool
568+isNoteBlock (NoteBlock _ _) = True
569+isNoteBlock _ = False
570+
571+isKeyBlock :: Reference -> Bool
572+isKeyBlock (KeyBlock _ _) = True
573+isKeyBlock _ = False
574+
575+isLineClump :: Reference -> Bool
576+isLineClump (LineClump _) = True
577+isLineClump _ = False
578+
579+data HeaderType
580+ = SingleHeader Char -- ^ Single line of characters underneath
581+ | DoubleHeader Char -- ^ Lines of characters above and below
582+ deriving (Eq, Show)
583+
584+data ParserContext
585+ = ListItemState -- ^ Used when running parser on list item contents
586+ | NullState -- ^ Default state
587+ deriving (Eq, Show)
588+
589+data QuoteContext
590+ = InSingleQuote -- ^ Used when parsing inside single quotes
591+ | InDoubleQuote -- ^ Used when parsing inside double quotes
592+ | NoQuote -- ^ Used when not parsing inside quotes
593+ deriving (Eq, Show)
594+
595+type NoteTable = [(String, [Block])]
596+
597+type KeyTable = [([Inline], Target)]
598+
599+-- | Look up key in key table and return target object.
600+lookupKeySrc :: KeyTable -- ^ Key table
601+ -> [Inline] -- ^ Key
602+ -> Maybe Target
603+lookupKeySrc table key = case find (refsMatch key . fst) table of
604+ Nothing -> Nothing
605+ Just (_, src) -> Just src
606+
607+-- | Returns @True@ if keys match (case insensitive).
608+refsMatch :: [Inline] -> [Inline] -> Bool
609+refsMatch ((Str x):restx) ((Str y):resty) =
610+ ((map toLower x) == (map toLower y)) && refsMatch restx resty
611+refsMatch ((Emph x):restx) ((Emph y):resty) =
612+ refsMatch x y && refsMatch restx resty
613+refsMatch ((Strong x):restx) ((Strong y):resty) =
614+ refsMatch x y && refsMatch restx resty
615+refsMatch ((Strikeout x):restx) ((Strikeout y):resty) =
616+ refsMatch x y && refsMatch restx resty
617+refsMatch ((Superscript x):restx) ((Superscript y):resty) =
618+ refsMatch x y && refsMatch restx resty
619+refsMatch ((Subscript x):restx) ((Subscript y):resty) =
620+ refsMatch x y && refsMatch restx resty
621+refsMatch ((Quoted t x):restx) ((Quoted u y):resty) =
622+ t == u && refsMatch x y && refsMatch restx resty
623+refsMatch ((Code x):restx) ((Code y):resty) =
624+ ((map toLower x) == (map toLower y)) && refsMatch restx resty
625+refsMatch ((TeX x):restx) ((TeX y):resty) =
626+ ((map toLower x) == (map toLower y)) && refsMatch restx resty
627+refsMatch ((HtmlInline x):restx) ((HtmlInline y):resty) =
628+ ((map toLower x) == (map toLower y)) && refsMatch restx resty
629+refsMatch (x:restx) (y:resty) = (x == y) && refsMatch restx resty
630+refsMatch [] x = null x
631+refsMatch x [] = null x
306632
633+--
634+-- Native format prettyprinting
635+--
636+
307637-- | Indent string as a block.
308638indentBy :: Int -- ^ Number of spaces to indent the block
309639 -> Int -- ^ Number of spaces (rel to block) to indent first line
@@ -311,9 +641,10 @@ indentBy :: Int -- ^ Number of spaces to indent the block
311641 -> String
312642indentBy num first [] = ""
313643indentBy num first str =
314− let (firstLine:restLines) = lines str
315− firstLineIndent = num + first in
316− (replicate firstLineIndent ' ') ++ firstLine ++ "\n" ++ (joinWithSep "\n" $ map (\line -> (replicate num ' ') ++ line) restLines)
644+ let (firstLine:restLines) = lines str
645+ firstLineIndent = num + first
646+ in (replicate firstLineIndent ' ') ++ firstLine ++ "\n" ++
647+ (joinWithSep "\n" $ map ((replicate num ' ') ++ ) restLines)
317648
318649-- | Prettyprint list of Pandoc blocks elements.
319650prettyBlockList :: Int -- ^ Number of spaces to indent list of blocks
@@ -321,142 +652,40 @@ prettyBlockList :: Int -- ^ Number of spaces to indent list of blocks
321652 -> String
322653prettyBlockList indent [] = indentBy indent 0 "[]"
323654prettyBlockList indent blocks = indentBy indent (-2) $ "[ " ++
324− (joinWithSep "\n, " (map prettyBlock blocks)) ++ " ]"
655+ (joinWithSep "\n, " (map prettyBlock blocks)) ++ " ]"
325656
326657-- | Prettyprint Pandoc block element.
327658prettyBlock :: Block -> String
328659prettyBlock (BlockQuote blocks) = "BlockQuote\n " ++
329660 (prettyBlockList 2 blocks)
330661prettyBlock (OrderedList attribs blockLists) =
331− "OrderedList " ++ show attribs ++ "\n" ++ indentBy 2 0 ("[ " ++
332− (joinWithSep ", " $ map (\blocks -> prettyBlockList 2 blocks)
333− blockLists)) ++ " ]"
662+ "OrderedList " ++ show attribs ++ "\n" ++ indentBy 2 0 ("[ " ++
663+ (joinWithSep ", " $ map (\blocks -> prettyBlockList 2 blocks)
664+ blockLists)) ++ " ]"
334665prettyBlock (BulletList blockLists) = "BulletList\n" ++
335− indentBy 2 0 ("[ " ++ (joinWithSep ", "
336− (map (\blocks -> prettyBlockList 2 blocks) blockLists))) ++ " ]"
666+ indentBy 2 0 ("[ " ++ (joinWithSep ", "
667+ (map (\blocks -> prettyBlockList 2 blocks) blockLists))) ++ " ]"
337668prettyBlock (DefinitionList blockLists) = "DefinitionList\n" ++
338− indentBy 2 0 ("[" ++ (joinWithSep ",\n"
339− (map (\(term, blocks) -> " (" ++ show term ++ ",\n" ++
340− indentBy 1 2 (prettyBlockList 2 blocks) ++ " )") blockLists))) ++ " ]"
669+ indentBy 2 0 ("[" ++ (joinWithSep ",\n"
670+ (map (\(term, blocks) -> " (" ++ show term ++ ",\n" ++
671+ indentBy 1 2 (prettyBlockList 2 blocks) ++ " )") blockLists))) ++ " ]"
341672prettyBlock (Table caption aligns widths header rows) =
342− "Table " ++ show caption ++ " " ++ show aligns ++ " " ++
343− show widths ++ "\n" ++ prettyRow header ++ " [\n" ++
344− (joinWithSep ",\n" (map prettyRow rows)) ++ " ]"
345− where prettyRow cols = indentBy 2 0 ("[ " ++ (joinWithSep ", "
346− (map (\blocks -> prettyBlockList 2 blocks)
347− cols))) ++ " ]"
673+ "Table " ++ show caption ++ " " ++ show aligns ++ " " ++
674+ show widths ++ "\n" ++ prettyRow header ++ " [\n" ++
675+ (joinWithSep ",\n" (map prettyRow rows)) ++ " ]"
676+ where prettyRow cols = indentBy 2 0 ("[ " ++ (joinWithSep ", "
677+ (map (\blocks -> prettyBlockList 2 blocks)
678+ cols))) ++ " ]"
348679prettyBlock block = show block
349680
350681-- | Prettyprint Pandoc document.
351682prettyPandoc :: Pandoc -> String
352−prettyPandoc (Pandoc meta blocks) = "Pandoc " ++ "(" ++ (show meta) ++
353− ")\n" ++ (prettyBlockList 0 blocks) ++ "\n"
354−
355−-- | Convert tabs to spaces (with adjustable tab stop).
356−tabsToSpaces :: Int -- ^ Tabstop
357− -> String -- ^ String to convert
358− -> String
359−tabsToSpaces tabstop str =
360− unlines (map (tabsInLine tabstop tabstop) (lines str))
683+prettyPandoc (Pandoc meta blocks) = "Pandoc " ++ "(" ++ show meta ++
684+ ")\n" ++ (prettyBlockList 0 blocks) ++ "\n"
361685
362−-- | Convert tabs to spaces in one line.
363−tabsInLine :: Int -- ^ Number of spaces to next tab stop
364− -> Int -- ^ Tabstop
365− -> String -- ^ Line to convert
366− -> String
367−tabsInLine num tabstop "" = ""
368−tabsInLine num tabstop (c:cs) =
369− let replacement = (if (c == '\t') then (replicate num ' ') else [c]) in
370− let nextnumraw = (num - (length replacement)) in
371− let nextnum = if (nextnumraw < 1)
372− then (nextnumraw + tabstop)
373− else nextnumraw in
374− replacement ++ (tabsInLine nextnum tabstop cs)
375−
376−-- | Returns an association list of backslash escapes for the
377−-- designated characters.
378−backslashEscapes :: [Char] -- ^ list of special characters to escape
379− -> [(Char, String)]
380−backslashEscapes = map (\ch -> (ch, ['\\',ch]))
381−
382−-- | Escape a string of characters, using an association list of
383−-- characters and strings.
384−escapeStringUsing :: [(Char, String)] -> String -> String
385−escapeStringUsing escapeTable "" = ""
386−escapeStringUsing escapeTable (x:xs) =
387− case (lookup x escapeTable) of
388− Just str -> str ++ rest
389− Nothing -> x:rest
390− where rest = escapeStringUsing escapeTable xs
391−
392−-- | Returns @True@ if string ends with given character.
393−endsWith :: Char -> [Char] -> Bool
394−endsWith char [] = False
395−endsWith char str = (char == last str)
396−
397−-- | Joins a list of lists, separated by another list.
398−joinWithSep :: [a] -- ^ List to use as separator
399− -> [[a]] -- ^ Lists to join
400− -> [a]
401−joinWithSep sep [] = []
402−joinWithSep sep lst = foldr1 (\a b -> a ++ sep ++ b) lst
403−
404−-- | Strip trailing newlines from string.
405−stripTrailingNewlines :: String -> String
406−stripTrailingNewlines "" = ""
407−stripTrailingNewlines str =
408− if (last str) == '\n'
409− then stripTrailingNewlines (init str)
410− else str
411−
412−-- | Remove leading and trailing space (including newlines) from string.
413−removeLeadingTrailingSpace :: String -> String
414−removeLeadingTrailingSpace = removeLeadingSpace . removeTrailingSpace
415−
416−-- | Remove leading space (including newlines) from string.
417−removeLeadingSpace :: String -> String
418−removeLeadingSpace = dropWhile (\x -> (x == ' ') || (x == '\n') ||
419− (x == '\t'))
420−
421−-- | Remove trailing space (including newlines) from string.
422−removeTrailingSpace :: String -> String
423−removeTrailingSpace = reverse . removeLeadingSpace . reverse
424−
425−-- | Strip leading and trailing characters from string
426−stripFirstAndLast :: String -> String
427−stripFirstAndLast str =
428− drop 1 $ take ((length str) - 1) str
429−
430−-- | Change CamelCase word to hyphenated lowercase (e.g., camel-case).
431−camelCaseToHyphenated :: String -> String
432−camelCaseToHyphenated "" = ""
433−camelCaseToHyphenated (a:b:rest) | isLower a && isUpper b =
434− a:'-':(toLower b):(camelCaseToHyphenated rest)
435−camelCaseToHyphenated (a:rest) = (toLower a):(camelCaseToHyphenated rest)
436−
437−-- | Replace each occurrence of one sublist in a list with another.
438−substitute :: (Eq a) => [a] -> [a] -> [a] -> [a]
439−substitute _ _ [] = []
440−substitute [] _ lst = lst
441−substitute target replacement lst =
442− if isPrefixOf target lst
443− then replacement ++ (substitute target replacement $ drop (length target) lst)
444− else (head lst):(substitute target replacement $ tail lst)
445−
446−-- | Split list into groups separated by sep.
447−splitBy :: (Eq a) => a -> [a] -> [[a]]
448−splitBy _ [] = []
449−splitBy sep lst =
450− let (first, rest) = break (== sep) lst
451− rest' = dropWhile (== sep) rest in
452− first:(splitBy sep rest')
453−
454−-- | Split list into chunks divided at specified indices.
455−splitByIndices :: [Int] -> [a] -> [[a]]
456−splitByIndices [] lst = [lst]
457−splitByIndices (x:xs) lst =
458− let (first, rest) = splitAt x lst in
459− first:(splitByIndices (map (\y -> y - x) xs) rest)
686+--
687+-- Pandoc block and inline list processing
688+--
460689
461690-- | Generate infinite lazy list of markers for an ordered list,
462691-- depending on list attributes.
@@ -466,8 +695,10 @@ orderedListMarkers (start, numstyle, numdelim) =
466695 seq = case numstyle of
467696 DefaultStyle -> map show [start..]
468697 Decimal -> map show [start..]
469− UpperAlpha -> drop (start - 1) $ cycle $ map singleton ['A'..'Z']
470− LowerAlpha -> drop (start - 1) $ cycle $ map singleton ['a'..'z']
698+ UpperAlpha -> drop (start - 1) $ cycle $
699+ map singleton ['A'..'Z']
700+ LowerAlpha -> drop (start - 1) $ cycle $
701+ map singleton ['a'..'z']
471702 UpperRoman -> map toRomanNumeral [start..]
472703 LowerRoman -> map (map toLower . toRomanNumeral) [start..]
473704 inDelim str = case numdelim of
@@ -477,27 +708,6 @@ orderedListMarkers (start, numstyle, numdelim) =
477708 TwoParens -> "(" ++ str ++ ")"
478709 in map inDelim seq
479710
480−-- | Convert number < 4000 to uppercase roman numeral.
481−toRomanNumeral :: Int -> String
482−toRomanNumeral x =
483− if x >= 4000 || x < 0
484− then "?"
485− else case x of
486− x | x >= 1000 -> "M" ++ toRomanNumeral (x - 1000)
487− x | x >= 900 -> "CM" ++ toRomanNumeral (x - 900)
488− x | x >= 500 -> "D" ++ toRomanNumeral (x - 500)
489− x | x >= 400 -> "CD" ++ toRomanNumeral (x - 400)
490− x | x >= 100 -> "C" ++ toRomanNumeral (x - 100)
491− x | x >= 90 -> "XC" ++ toRomanNumeral (x - 90)
492− x | x >= 50 -> "L" ++ toRomanNumeral (x - 50)
493− x | x >= 40 -> "XL" ++ toRomanNumeral (x - 40)
494− x | x >= 10 -> "X" ++ toRomanNumeral (x - 10)
495− x | x >= 9 -> "IX" ++ toRomanNumeral (x - 5)
496− x | x >= 5 -> "V" ++ toRomanNumeral (x - 5)
497− x | x >= 4 -> "IV" ++ toRomanNumeral (x - 4)
498− x | x >= 1 -> "I" ++ toRomanNumeral (x - 1)
499− 0 -> ""
500−
501711-- | Normalize a list of inline elements: remove leading and trailing
502712-- @Space@ elements, collapse double @Space@s into singles, and
503713-- remove empty Str elements.
@@ -507,16 +717,14 @@ normalizeSpaces list =
507717 let removeDoubles [] = []
508718 removeDoubles (Space:Space:rest) = removeDoubles (Space:rest)
509719 removeDoubles ((Str ""):rest) = removeDoubles rest
510− removeDoubles (x:rest) = x:(removeDoubles rest) in
511− let removeLeading [] = []
512− removeLeading lst = if ((head lst) == Space)
513− then tail lst
514− else lst in
515− let removeTrailing [] = []
516− removeTrailing lst = if ((last lst) == Space)
517− then init lst
518− else lst in
519− removeLeading $ removeTrailing $ removeDoubles list
720+ removeDoubles (x:rest) = x:(removeDoubles rest)
721+ removeLeading (Space:xs) = removeLeading xs
722+ removeLeading x = x
723+ removeTrailing [] = []
724+ removeTrailing lst = if (last lst == Space)
725+ then init lst
726+ else lst
727+ in removeLeading $ removeTrailing $ removeDoubles list
520728
521729-- | Change final list item from @Para@ to @Plain@ if the list should
522730-- be compact.
@@ -524,122 +732,86 @@ compactify :: [[Block]] -- ^ List of list items (each a list of blocks)
524732 -> [[Block]]
525733compactify [] = []
526734compactify items =
527− let final = last items
528− others = init items in
529− case final of
530− [Para a] -> if any containsPara others
531− then items
532− else others ++ [[Plain a]]
533− otherwise -> items
735+ let final = last items
736+ others = init items
737+ in case final of
738+ [Para a] -> if any containsPara others
739+ then items
740+ else others ++ [[Plain a]]
741+ otherwise -> items
534742
535743containsPara :: [Block] -> Bool
536744containsPara [] = False
537745containsPara ((Para a):rest) = True
538−containsPara ((BulletList items):rest) = (any containsPara items) ||
539− (containsPara rest)
540−containsPara ((OrderedList _ items):rest) = (any containsPara items) ||
541− (containsPara rest)
746+containsPara ((BulletList items):rest) = any containsPara items ||
747+ containsPara rest
748+containsPara ((OrderedList _ items):rest) = any containsPara items ||
749+ containsPara rest
750+containsPara ((DefinitionList items):rest) = any containsPara (map snd items) ||
751+ containsPara rest
542752containsPara (x:rest) = containsPara rest
543753
544754-- | Data structure for defining hierarchical Pandoc documents
545755data Element = Blk Block
546756 | Sec [Inline] [Element] deriving (Eq, Read, Show)
547757
548−-- | Returns true on Header block with level at least 'level'
758+-- | Returns @True@ on Header block with at least the specified level
549759headerAtLeast :: Int -> Block -> Bool
550760headerAtLeast level (Header x _) = x <= level
551761headerAtLeast level _ = False
552762
553−-- | Convert list of Pandoc blocks into list of Elements (hierarchical)
763+-- | Convert list of Pandoc blocks into (hierarchical) list of Elements
554764hierarchicalize :: [Block] -> [Element]
555765hierarchicalize [] = []
556766hierarchicalize (block:rest) =
557767 case block of
558− (Header level title) -> let (thisSection, rest') = break (headerAtLeast
559− level) rest in
560− (Sec title (hierarchicalize thisSection)):
561− (hierarchicalize rest')
562− x -> (Blk x):(hierarchicalize rest)
768+ (Header level title) ->
769+ let (thisSection, rest') = break (headerAtLeast level) rest
770+ in (Sec title (hierarchicalize thisSection)):(hierarchicalize rest')
771+ x -> (Blk x):(hierarchicalize rest)
563772
564773-- | True if block is a Header block.
565774isHeaderBlock :: Block -> Bool
566775isHeaderBlock (Header _ _) = True
567776isHeaderBlock _ = False
568777
778+--
779+-- Writer options
780+--
781+
569782-- | Options for writers
570783data WriterOptions = WriterOptions
571− { writerStandalone :: Bool -- ^ Include header and footer
572− , writerTitlePrefix :: String -- ^ Prefix for HTML titles
573− , writerHeader :: String -- ^ Header for the document
574− , writerIncludeBefore :: String -- ^ String to include before the body
575− , writerIncludeAfter :: String -- ^ String to include after the body
576− , writerTableOfContents :: Bool -- ^ Include table of contents
577− , writerS5 :: Bool -- ^ We're writing S5
578− , writerUseASCIIMathML :: Bool -- ^ Use ASCIIMathML
579− , writerASCIIMathMLURL :: Maybe String -- ^ URL to asciiMathML.js
580− , writerIgnoreNotes :: Bool -- ^ Ignore footnotes (used in making toc)
581− , writerIncremental :: Bool -- ^ Incremental S5 lists
582− , writerNumberSections :: Bool -- ^ Number sections in LaTeX
583− , writerStrictMarkdown :: Bool -- ^ Use strict markdown syntax
584− , writerReferenceLinks :: Bool -- ^ Use reference links in writing markdown, rst
585− , writerTabStop :: Int -- ^ Tabstop for conversion between
586− -- spaces and tabs
587− } deriving Show
784+ { writerStandalone :: Bool -- ^ Include header and footer
785+ , writerHeader :: String -- ^ Header for the document
786+ , writerTitlePrefix :: String -- ^ Prefix for HTML titles
787+ , writerTabStop :: Int -- ^ Tabstop for conversion btw spaces and tabs
788+ , writerTableOfContents :: Bool -- ^ Include table of contents
789+ , writerS5 :: Bool -- ^ We're writing S5
790+ , writerUseASCIIMathML :: Bool -- ^ Use ASCIIMathML
791+ , writerASCIIMathMLURL :: Maybe String -- ^ URL to asciiMathML.js
792+ , writerIgnoreNotes :: Bool -- ^ Ignore footnotes (used in making toc)
793+ , writerIncremental :: Bool -- ^ Incremental S5 lists
794+ , writerNumberSections :: Bool -- ^ Number sections in LaTeX
795+ , writerIncludeBefore :: String -- ^ String to include before the body
796+ , writerIncludeAfter :: String -- ^ String to include after the body
797+ , writerStrictMarkdown :: Bool -- ^ Use strict markdown syntax
798+ , writerReferenceLinks :: Bool -- ^ Use reference links in writing markdown, rst
799+ } deriving Show
588800
589801-- | Default writer options.
590802defaultWriterOptions =
591− WriterOptions { writerStandalone = False,
592− writerHeader = "",
593− writerTitlePrefix = "",
594− writerTabStop = 4,
595− writerTableOfContents = False,
596− writerS5 = False,
597− writerUseASCIIMathML = False,
598− writerASCIIMathMLURL = Nothing,
599− writerIgnoreNotes = False,
600− writerIncremental = False,
601− writerNumberSections = False,
602− writerIncludeBefore = "",
603− writerIncludeAfter = "",
604− writerStrictMarkdown = False,
605− writerReferenceLinks = False }
606−
607−--
608−-- code to lookup reference keys in key table
609−--
610−
611−-- | Look up key in key table and return target object.
612−lookupKeySrc :: KeyTable -- ^ Key table
613− -> [Inline] -- ^ Key
614− -> Maybe Target
615−lookupKeySrc table key = case table of
616− [] -> Nothing
617− (k, src):rest -> if (refsMatch k key)
618− then Just src
619− else lookupKeySrc rest key
620−
621−-- | Returns @True@ if keys match (case insensitive).
622−refsMatch :: [Inline] -> [Inline] -> Bool
623−refsMatch ((Str x):restx) ((Str y):resty) =
624− ((map toLower x) == (map toLower y)) && refsMatch restx resty
625−refsMatch ((Emph x):restx) ((Emph y):resty) =
626− refsMatch x y && refsMatch restx resty
627−refsMatch ((Strong x):restx) ((Strong y):resty) =
628− refsMatch x y && refsMatch restx resty
629−refsMatch ((Strikeout x):restx) ((Strikeout y):resty) =
630− refsMatch x y && refsMatch restx resty
631−refsMatch ((Superscript x):restx) ((Superscript y):resty) =
632− refsMatch x y && refsMatch restx resty
633−refsMatch ((Subscript x):restx) ((Subscript y):resty) =
634− refsMatch x y && refsMatch restx resty
635−refsMatch ((Quoted t x):restx) ((Quoted u y):resty) =
636− t == u && refsMatch x y && refsMatch restx resty
637−refsMatch ((Code x):restx) ((Code y):resty) =
638− ((map toLower x) == (map toLower y)) && refsMatch restx resty
639−refsMatch ((TeX x):restx) ((TeX y):resty) =
640− ((map toLower x) == (map toLower y)) && refsMatch restx resty
641−refsMatch ((HtmlInline x):restx) ((HtmlInline y):resty) =
642− ((map toLower x) == (map toLower y)) && refsMatch restx resty
643−refsMatch (x:restx) (y:resty) = (x == y) && refsMatch restx resty
644−refsMatch [] x = null x
645−refsMatch x [] = null x
803+ WriterOptions { writerStandalone = False,
804+ writerHeader = "",
805+ writerTitlePrefix = "",
806+ writerTabStop = 4,
807+ writerTableOfContents = False,
808+ writerS5 = False,
809+ writerUseASCIIMathML = False,
810+ writerASCIIMathMLURL = Nothing,
811+ writerIgnoreNotes = False,
812+ writerIncremental = False,
813+ writerNumberSections = False,
814+ writerIncludeBefore = "",
815+ writerIncludeAfter = "",
816+ writerStrictMarkdown = False,
817+ writerReferenceLinks = False }
src/Text/Pandoc/Writers/ConTeXt.hs+10−13
@@ -18,7 +18,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1818
1919{- |
2020 Module : Text.Pandoc.Writers.ConTeXt
21− Copyright : Copyright (C) 2006-7 John MacFarlane
21+ Copyright : Copyright (C) 2007 John MacFarlane
2222 License : GNU GPL, version 2 or above
2323
2424 Maintainer : John MacFarlane <jgm@berkeley.edu>
@@ -27,9 +27,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2727
2828Conversion of 'Pandoc' format into ConTeXt.
2929-}
30−module Text.Pandoc.Writers.ConTeXt (
31− writeConTeXt
32− ) where
30+module Text.Pandoc.Writers.ConTeXt ( writeConTeXt ) where
3331import Text.Pandoc.Definition
3432import Text.Pandoc.Shared
3533import Text.Printf ( printf )
@@ -40,8 +38,7 @@ type WriterState = Int -- number of next URL reference
4038
4139-- | Convert Pandoc to ConTeXt.
4240writeConTeXt :: WriterOptions -> Pandoc -> String
43−writeConTeXt options document =
44− evalState (pandocToConTeXt options document) 1
41+writeConTeXt options document = evalState (pandocToConTeXt options document) 1
4542
4643pandocToConTeXt :: WriterOptions -> Pandoc -> State WriterState String
4744pandocToConTeXt options (Pandoc meta blocks) = do
@@ -111,8 +108,8 @@ stringToConTeXt = concatMap escapeCharForConTeXt
111108-- | Convert Pandoc block element to ConTeXt.
112109blockToConTeXt :: Block -> State WriterState String
113110blockToConTeXt Null = return ""
114−blockToConTeXt (Plain lst) = inlineListToConTeXt lst >>= (return . (++ "\n"))
115−blockToConTeXt (Para lst) = inlineListToConTeXt lst >>= (return . (++ "\n\n"))
111+blockToConTeXt (Plain lst) = inlineListToConTeXt lst >>= return . (++ "\n")
112+blockToConTeXt (Para lst) = inlineListToConTeXt lst >>= return . (++ "\n\n")
116113blockToConTeXt (BlockQuote lst) = do
117114 contents <- blockListToConTeXt lst
118115 return $ "\\startblockquote\n" ++ contents ++ "\\stopblockquote\n\n"
@@ -137,12 +134,12 @@ blockToConTeXt (OrderedList attribs lst) = case attribs of
137134 return $ "\\startitemize" ++ markerWidth' ++ "\n" ++ concat contents ++
138135 "\\stopitemize\n"
139136blockToConTeXt (DefinitionList lst) =
140− mapM defListItemToConTeXt lst >>= (return . (++ "\n") . concat)
137+ mapM defListItemToConTeXt lst >>= return . (++ "\n") . concat
141138blockToConTeXt HorizontalRule = return "\\thinrule\n\n"
142139blockToConTeXt (Header level lst) = do
143140 contents <- inlineListToConTeXt lst
144− return $ if (level > 0) && (level <= 3)
145− then "\\" ++ (concat (replicate (level - 1) "sub")) ++
141+ return $ if level > 0 && level <= 3
142+ then "\\" ++ concat (replicate (level - 1) "sub") ++
146143 "section{" ++ contents ++ "}\n\n"
147144 else contents ++ "\n\n"
148145blockToConTeXt (Table caption aligns widths heads rows) = do
@@ -186,12 +183,12 @@ defListItemToConTeXt (term, def) = do
186183
187184-- | Convert list of block elements to ConTeXt.
188185blockListToConTeXt :: [Block] -> State WriterState String
189−blockListToConTeXt lst = mapM blockToConTeXt lst >>= (return . concat)
186+blockListToConTeXt lst = mapM blockToConTeXt lst >>= return . concat
190187
191188-- | Convert list of inline elements to ConTeXt.
192189inlineListToConTeXt :: [Inline] -- ^ Inlines to convert
193190 -> State WriterState String
194−inlineListToConTeXt lst = mapM inlineToConTeXt lst >>= (return . concat)
191+inlineListToConTeXt lst = mapM inlineToConTeXt lst >>= return . concat
195192
196193isQuoted :: Inline -> Bool
197194isQuoted (Quoted _ _) = True
src/Text/Pandoc/Writers/Docbook.hs+104−95
@@ -30,16 +30,35 @@ Conversion of 'Pandoc' documents to Docbook XML.
3030module Text.Pandoc.Writers.Docbook ( writeDocbook) where
3131import Text.Pandoc.Definition
3232import Text.Pandoc.Shared
33−import Text.Pandoc.Entities ( escapeStringForXML )
34−import Data.Char ( toLower, ord )
35−import Data.List ( isPrefixOf, partition, drop )
33+import Data.List ( isPrefixOf, drop )
3634import Text.PrettyPrint.HughesPJ hiding ( Str )
3735
38−
3936--
4037-- code to format XML
4138--
4239
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+
4362-- | Return a text object with a string of formatted XML attributes.
4463attributeList :: [(String, String)] -> Doc
4564attributeList = text . concatMap
@@ -52,10 +71,10 @@ inTags:: Bool -> String -> [(String, String)] -> Doc -> Doc
5271inTags isIndented tagType attribs contents =
5372 let openTag = char '<' <> text tagType <> attributeList attribs <>
5473 char '>'
55− closeTag = text "</" <> text tagType <> char '>' in
56− if isIndented
57− then openTag $$ nest 2 contents $$ closeTag
58− else openTag <> contents <> closeTag
74+ closeTag = text "</" <> text tagType <> char '>'
75+ in if isIndented
76+ then openTag $$ nest 2 contents $$ closeTag
77+ else openTag <> contents <> closeTag
5978
6079-- | Return a self-closing tag of tagType with specified attributes
6180selfClosingTag :: String -> [(String, String)] -> Doc
@@ -79,42 +98,42 @@ authorToDocbook :: [Char] -> Doc
7998authorToDocbook name = inTagsIndented "author" $
8099 if ',' `elem` name
81100 then -- last name first
82− let (lastname, rest) = break (==',') name
83− firstname = removeLeadingSpace rest in
84− inTagsSimple "firstname" (text $ escapeStringForXML firstname) <>
85− inTagsSimple "surname" (text $ escapeStringForXML lastname)
101+ let (lastname, rest) = break (==',') name
102+ firstname = removeLeadingSpace rest in
103+ inTagsSimple "firstname" (text $ escapeStringForXML firstname) <>
104+ inTagsSimple "surname" (text $ escapeStringForXML lastname)
86105 else -- last name last
87− let namewords = words name
88− lengthname = length namewords
89− (firstname, lastname) = case lengthname of
90− 0 -> ("","")
91− 1 -> ("", name)
92− n -> (joinWithSep " " (take (n-1) namewords), last namewords) in
93− inTagsSimple "firstname" (text $ escapeStringForXML firstname) $$
94− inTagsSimple "surname" (text $ escapeStringForXML lastname)
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)
95114
96115-- | Convert Pandoc document to string in Docbook format.
97116writeDocbook :: WriterOptions -> Pandoc -> String
98117writeDocbook opts (Pandoc (Meta title authors date) blocks) =
99− let head = if (writerStandalone opts)
100− then text (writerHeader opts)
101− else empty
102− meta = if (writerStandalone opts)
103− then inTagsIndented "articleinfo" $
104− (inTagsSimple "title" (wrap opts title)) $$
105− (vcat (map authorToDocbook authors)) $$
106− (inTagsSimple "date" (text $ escapeStringForXML date))
107− else empty
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
108127 elements = hierarchicalize blocks
109− before = writerIncludeBefore opts
110− after = writerIncludeAfter opts
111− body = (if null before then empty else text before) $$
112− vcat (map (elementToDocbook opts) elements) $$
113− (if null after then empty else text after)
114− body' = if writerStandalone opts
115− then inTagsIndented "article" (meta $$ body)
116− else body in
117− render $ head $$ body' $$ text ""
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 ""
118137
119138-- | Convert an Element to Docbook.
120139elementToDocbook :: WriterOptions -> Element -> Doc
@@ -123,10 +142,10 @@ elementToDocbook opts (Sec title elements) =
123142 -- Docbook doesn't allow sections with no content, so insert some if needed
124143 let elements' = if null elements
125144 then [Blk (Para [])]
126− else elements in
127− inTagsIndented "section" $
128− inTagsSimple "title" (wrap opts title) $$
129− vcat (map (elementToDocbook opts) elements')
145+ else elements
146+ in inTagsIndented "section" $
147+ inTagsSimple "title" (wrap opts title) $$
148+ vcat (map (elementToDocbook opts) elements')
130149
131150-- | Convert a list of Pandoc blocks to Docbook.
132151blocksToDocbook :: WriterOptions -> [Block] -> Doc
@@ -145,30 +164,27 @@ deflistItemsToDocbook opts items =
145164-- | Convert a term and a list of blocks into a Docbook varlistentry.
146165deflistItemToDocbook :: WriterOptions -> [Inline] -> [Block] -> Doc
147166deflistItemToDocbook opts term def =
148− let def' = map plainToPara def in
149− inTagsIndented "varlistentry" $
150− inTagsIndented "term" (inlinesToDocbook opts term) $$
151− inTagsIndented "listitem" (blocksToDocbook opts def')
167+ let def' = map plainToPara def
168+ in inTagsIndented "varlistentry" $
169+ inTagsIndented "term" (inlinesToDocbook opts term) $$
170+ inTagsIndented "listitem" (blocksToDocbook opts def')
152171
153172-- | Convert a list of lists of blocks to a list of Docbook list items.
154173listItemsToDocbook :: WriterOptions -> [[Block]] -> Doc
155−listItemsToDocbook opts items =
156− vcat $ map (listItemToDocbook opts) items
174+listItemsToDocbook opts items = vcat $ map (listItemToDocbook opts) items
157175
158176-- | Convert a list of blocks into a Docbook list item.
159177listItemToDocbook :: WriterOptions -> [Block] -> Doc
160178listItemToDocbook opts item =
161− let item' = map plainToPara item in
162− inTagsIndented "listitem" (blocksToDocbook opts item')
179+ inTagsIndented "listitem" $ blocksToDocbook opts $ map plainToPara item
163180
164181-- | Convert a Pandoc block element to Docbook.
165182blockToDocbook :: WriterOptions -> Block -> Doc
166183blockToDocbook opts Null = empty
167184blockToDocbook opts (Plain lst) = wrap opts lst
168−blockToDocbook opts (Para lst) =
169− inTagsIndented "para" (wrap opts lst)
185+blockToDocbook opts (Para lst) = inTagsIndented "para" $ wrap opts lst
170186blockToDocbook opts (BlockQuote blocks) =
171− inTagsIndented "blockquote" (blocksToDocbook opts blocks)
187+ inTagsIndented "blockquote" $ blocksToDocbook opts blocks
172188blockToDocbook opts (CodeBlock str) =
173189 text "<screen>\n" <> text (escapeStringForXML str) <> text "\n</screen>"
174190blockToDocbook opts (BulletList lst) =
@@ -198,16 +214,16 @@ blockToDocbook opts (Table caption aligns widths headers rows) =
198214 then empty
199215 else inTagsIndented "caption"
200216 (inlinesToDocbook opts caption)
201− tableType = if isEmpty captionDoc then "informaltable" else "table" in
202− inTagsIndented tableType $ captionDoc $$
203− (colHeadsToDocbook opts alignStrings widths headers) $$
204− (vcat $ map (tableRowToDocbook opts alignStrings) rows)
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)
205221
206222colHeadsToDocbook opts alignStrings widths headers =
207− let heads = zipWith3
208− (\align width item -> tableItemToDocbook opts "th" align width item)
209− alignStrings widths headers in
210− inTagsIndented "tr" $ vcat heads
223+ let heads = zipWith3 (\align width item ->
224+ tableItemToDocbook opts "th" align width item)
225+ alignStrings widths headers
226+ in inTagsIndented "tr" $ vcat heads
211227
212228alignmentToString alignment = case alignment of
213229 AlignLeft -> "left"
@@ -215,20 +231,16 @@ alignmentToString alignment = case alignment of
215231 AlignCenter -> "center"
216232 AlignDefault -> "left"
217233
218−tableRowToDocbook opts aligns cols =
219− inTagsIndented "tr" $ vcat $ zipWith3 (tableItemToDocbook opts "td") aligns (repeat 0) cols
234+tableRowToDocbook opts aligns cols = inTagsIndented "tr" $
235+ vcat $ zipWith3 (tableItemToDocbook opts "td") aligns (repeat 0) cols
220236
221237tableItemToDocbook opts tag align width item =
222238 let attrib = [("align", align)] ++
223− if (width /= 0)
224− then [("style", "{width: " ++
225− show (truncate (100*width)) ++ "%;}")]
226− else [] in
227− inTags True tag attrib $ vcat $ map (blockToDocbook opts) item
228−
229−-- | Put string in CDATA section
230−cdata :: String -> Doc
231−cdata str = text $ "<![CDATA[" ++ str ++ "]]>"
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
232244
233245-- | Take list of inline elements and return wrapped doc.
234246wrap :: WriterOptions -> [Inline] -> Doc
@@ -236,25 +248,24 @@ wrap opts lst = fsep $ map (inlinesToDocbook opts) (splitBy Space lst)
236248
237249-- | Convert a list of inline elements to Docbook.
238250inlinesToDocbook :: WriterOptions -> [Inline] -> Doc
239−inlinesToDocbook opts lst = hcat (map (inlineToDocbook opts) lst)
251+inlinesToDocbook opts lst = hcat $ map (inlineToDocbook opts) lst
240252
241253-- | Convert an inline element to Docbook.
242254inlineToDocbook :: WriterOptions -> Inline -> Doc
243255inlineToDocbook opts (Str str) = text $ escapeStringForXML str
244256inlineToDocbook opts (Emph lst) =
245− inTagsSimple "emphasis" (inlinesToDocbook opts lst)
257+ inTagsSimple "emphasis" $ inlinesToDocbook opts lst
246258inlineToDocbook opts (Strong lst) =
247− inTags False "emphasis" [("role", "strong")]
248− (inlinesToDocbook opts lst)
259+ inTags False "emphasis" [("role", "strong")] $ inlinesToDocbook opts lst
249260inlineToDocbook opts (Strikeout lst) =
250− inTags False "emphasis" [("role", "strikethrough")]
251− (inlinesToDocbook opts lst)
261+ inTags False "emphasis" [("role", "strikethrough")] $
262+ inlinesToDocbook opts lst
252263inlineToDocbook opts (Superscript lst) =
253− inTagsSimple "superscript" (inlinesToDocbook opts lst)
264+ inTagsSimple "superscript" $ inlinesToDocbook opts lst
254265inlineToDocbook opts (Subscript lst) =
255− inTagsSimple "subscript" (inlinesToDocbook opts lst)
266+ inTagsSimple "subscript" $ inlinesToDocbook opts lst
256267inlineToDocbook opts (Quoted _ lst) =
257− inTagsSimple "quote" (inlinesToDocbook opts lst)
268+ inTagsSimple "quote" $ inlinesToDocbook opts lst
258269inlineToDocbook opts Apostrophe = char '\''
259270inlineToDocbook opts Ellipses = text "&#8230;"
260271inlineToDocbook opts EmDash = text "&#8212;"
@@ -263,26 +274,24 @@ inlineToDocbook opts (Code str) =
263274 inTagsSimple "literal" $ text (escapeStringForXML str)
264275inlineToDocbook opts (TeX str) = inlineToDocbook opts (Code str)
265276inlineToDocbook opts (HtmlInline str) = empty
266−inlineToDocbook opts LineBreak =
267− text $ "<literallayout></literallayout>"
277+inlineToDocbook opts LineBreak = text $ "<literallayout></literallayout>"
268278inlineToDocbook opts Space = char ' '
269279inlineToDocbook opts (Link txt (src, tit)) =
270280 if isPrefixOf "mailto:" src
271− then let src' = drop 7 src
272− emailLink = inTagsSimple "email" $ text (escapeStringForXML $ src')
273− in if txt == [Code src']
274− then emailLink
275− else inlinesToDocbook opts txt <+> char '(' <> emailLink <>
276− char ')'
277− else inTags False "ulink" [("url", src)] $ inlinesToDocbook opts txt
281+ then let src' = drop 7 src
282+ emailLink = inTagsSimple "email" $ text $
283+ escapeStringForXML $ src'
284+ in if txt == [Code src']
285+ then emailLink
286+ else inlinesToDocbook opts txt <+> char '(' <> emailLink <>
287+ char ')'
288+ else inTags False "ulink" [("url", src)] $ inlinesToDocbook opts txt
278289inlineToDocbook opts (Image alt (src, tit)) =
279290 let titleDoc = if null tit
280291 then empty
281292 else inTagsIndented "objectinfo" $
282− inTagsIndented "title"
283− (text $ escapeStringForXML tit) in
284− inTagsIndented "inlinemediaobject" $
285− inTagsIndented "imageobject" $
286− titleDoc $$ selfClosingTag "imagedata" [("fileref", src)]
293+ inTagsIndented "title" (text $ escapeStringForXML tit)
294+ in inTagsIndented "inlinemediaobject" $ inTagsIndented "imageobject" $
295+ titleDoc $$ selfClosingTag "imagedata" [("fileref", src)]
287296inlineToDocbook opts (Note contents) =
288297 inTagsIndented "footnote" $ blocksToDocbook opts contents
src/Text/Pandoc/Writers/HTML.hs+241−234
@@ -27,15 +27,15 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2727
2828Conversion of 'Pandoc' documents to HTML.
2929-}
30−module Text.Pandoc.Writers.HTML ( writeHtml, writeHtmlString ) where
30+module Text.Pandoc.Writers.HTML ( writeHtml , writeHtmlString ) where
3131import Text.Pandoc.Definition
3232import Text.Pandoc.ASCIIMathML
33+import Text.Pandoc.CharacterReferences ( decodeCharacterReferences )
3334import Text.Pandoc.Shared
34−import Text.Pandoc.Entities (decodeEntities)
3535import Text.Regex ( mkRegex, matchRegex )
3636import Numeric ( showHex )
3737import Data.Char ( ord, toLower )
38−import Data.List ( isPrefixOf, partition, intersperse )
38+import Data.List ( isPrefixOf, intersperse )
3939import qualified Data.Set as S
4040import Control.Monad.State
4141import Text.XHtml.Transitional
@@ -55,8 +55,8 @@ defaultWriterState = WriterState {stNotes= [], stIds = [],
5555writeHtmlString :: WriterOptions -> Pandoc -> String
5656writeHtmlString opts =
5757 if writerStandalone opts
58− then renderHtml . (writeHtml opts)
59− else renderHtmlFragment . (writeHtml opts)
58+ then renderHtml . writeHtml opts
59+ else renderHtmlFragment . writeHtml opts
6060
6161-- | Convert Pandoc document to Html structure.
6262writeHtml :: WriterOptions -> Pandoc -> Html
@@ -74,49 +74,51 @@ writeHtml opts (Pandoc (Meta tit authors date) blocks) =
7474 map (\a -> meta ! [name "author", content a]) authors) +++
7575 (if null date
7676 then noHtml
77− else meta ! [name "date", content date])
78− titleHeader = if (writerStandalone opts) && (not (null tit)) &&
79− (not (writerS5 opts))
77+ else meta ! [name "date", content date])
78+ titleHeader = if writerStandalone opts && not (null tit) &&
79+ not (writerS5 opts)
8080 then h1 ! [theclass "title"] $ topTitle
8181 else noHtml
8282 headerBlocks = filter isHeaderBlock blocks
83− ids = uniqueIdentifiers $ map (\(Header _ lst) -> lst) headerBlocks
84− toc = if writerTableOfContents opts
85− then tableOfContents opts headerBlocks ids
86− else noHtml
83+ ids = uniqueIdentifiers $
84+ map (\(Header _ lst) -> lst) headerBlocks
85+ toc = if writerTableOfContents opts
86+ then tableOfContents opts headerBlocks ids
87+ else noHtml
8788 (blocks', newstate) =
88− runState (blockListToHtml opts blocks)
89− (defaultWriterState {stIds = ids})
90− cssLines = stCSS newstate
91− css = if S.null cssLines
92− then noHtml
93− else style ! [thetype "text/css"] $ primHtml $
94− '\n':(unlines $ S.toList cssLines)
95− math = if stMath newstate
96− then case writerASCIIMathMLURL opts of
97− Just path -> script ! [src path,
98− thetype "text/javascript"] $ noHtml
99− Nothing -> primHtml asciiMathMLScript
100− else noHtml
101− head = header $ metadata +++ math +++ css +++
102− primHtml (writerHeader opts)
103− notes = reverse (stNotes newstate)
104− before = primHtml $ writerIncludeBefore opts
105− after = primHtml $ writerIncludeAfter opts
106− thebody = before +++ titleHeader +++ toc +++ blocks' +++
107− footnoteSection opts notes +++ after
89+ runState (blockListToHtml opts blocks)
90+ (defaultWriterState {stIds = ids})
91+ cssLines = stCSS newstate
92+ css = if S.null cssLines
93+ then noHtml
94+ else style ! [thetype "text/css"] $ primHtml $
95+ '\n':(unlines $ S.toList cssLines)
96+ math = if stMath newstate
97+ then case writerASCIIMathMLURL opts of
98+ Just path -> script ! [src path,
99+ thetype "text/javascript"] $
100+ noHtml
101+ Nothing -> primHtml asciiMathMLScript
102+ else noHtml
103+ head = header $ metadata +++ math +++ css +++
104+ primHtml (writerHeader opts)
105+ notes = reverse (stNotes newstate)
106+ before = primHtml $ writerIncludeBefore opts
107+ after = primHtml $ writerIncludeAfter opts
108+ thebody = before +++ titleHeader +++ toc +++ blocks' +++
109+ footnoteSection opts notes +++ after
108110 in if writerStandalone opts
109− then head +++ (body thebody)
111+ then head +++ body thebody
110112 else thebody
111113
112114-- | Construct table of contents from list of header blocks and identifiers.
113115-- Assumes there are as many identifiers as header blocks.
114116tableOfContents :: WriterOptions -> [Block] -> [String] -> Html
115117tableOfContents opts headers ids =
116− let opts' = opts { writerIgnoreNotes = True }
118+ let opts' = opts { writerIgnoreNotes = True }
117119 contentsTree = hierarchicalize headers
118− contents = evalState (mapM (elementToListItem opts') contentsTree)
119− (defaultWriterState {stIds = ids})
120+ contents = evalState (mapM (elementToListItem opts') contentsTree)
121+ (defaultWriterState {stIds = ids})
120122 in thediv ! [identifier "toc"] $ unordList contents
121123
122124-- | Converts an Element to a list item for a table of contents,
@@ -135,7 +137,8 @@ elementToListItem opts (Sec headerText subsecs) = do
135137 let subList = if null subHeads
136138 then noHtml
137139 else unordList subHeads
138− return $ (anchor ! [href ("#" ++ id), identifier ("TOC-" ++ id)] $ txt) +++ subList
140+ return $ (anchor ! [href ("#" ++ id), identifier ("TOC-" ++ id)] $ txt) +++
141+ subList
139142
140143-- | Convert list of Note blocks to a footnote <div>.
141144-- Assumes notes are sorted.
@@ -143,62 +146,61 @@ footnoteSection :: WriterOptions -> [Html] -> Html
143146footnoteSection opts notes =
144147 if null notes
145148 then noHtml
146− else thediv ! [theclass "footnotes"] $
147− hr +++ (olist << notes)
149+ else thediv ! [theclass "footnotes"] $ hr +++ (olist << notes)
148150
149151-- | Obfuscate a "mailto:" link using Javascript.
150152obfuscateLink :: WriterOptions -> String -> String -> Html
151153obfuscateLink opts text src =
152154 let emailRegex = mkRegex "^mailto:([^@]*)@(.*)$"
153− src' = map toLower src in
154− case (matchRegex emailRegex src') of
155− (Just [name, domain]) ->
156− let domain' = substitute "." " dot " domain
157− at' = obfuscateChar '@'
158− (linkText, altText) =
159− if text == drop 7 src' -- autolink
160− then ("'<code>'+e+'</code>'", name ++ " at " ++ domain')
161− else ("'" ++ text ++ "'", text ++ " (" ++ name ++ " at " ++
162− domain' ++ ")") in
163− if writerStrictMarkdown opts
164− then -- need to use primHtml or &'s are escaped to &amp; in URL
165− primHtml $ "<a href=\"" ++ (obfuscateString src')
166− ++ "\">" ++ (obfuscateString text) ++ "</a>"
167− else (script ! [thetype "text/javascript"] $
168− primHtml ("\n<!--\nh='" ++
169− obfuscateString domain ++ "';a='" ++ at' ++ "';n='" ++
170− obfuscateString name ++ "';e=n+a+h;\n" ++
171− "document.write('<a h'+'ref'+'=\"ma'+'ilto'+':'+e+'\">'+" ++
172− linkText ++ "+'<\\/'+'a'+'>');\n// -->\n")) +++
173− noscript (primHtml $ obfuscateString altText)
174− _ -> anchor ! [href src] $ primHtml text -- malformed email
155+ src' = map toLower src
156+ in case (matchRegex emailRegex src') of
157+ (Just [name, domain]) ->
158+ let domain' = substitute "." " dot " domain
159+ at' = obfuscateChar '@'
160+ (linkText, altText) =
161+ if text == drop 7 src' -- autolink
162+ then ("'<code>'+e+'</code>'", name ++ " at " ++ domain')
163+ else ("'" ++ text ++ "'", text ++ " (" ++ name ++ " at " ++
164+ domain' ++ ")")
165+ in if writerStrictMarkdown opts
166+ then -- need to use primHtml or &'s are escaped to &amp; in URL
167+ primHtml $ "<a href=\"" ++ (obfuscateString src')
168+ ++ "\">" ++ (obfuscateString text) ++ "</a>"
169+ else (script ! [thetype "text/javascript"] $
170+ primHtml ("\n<!--\nh='" ++
171+ obfuscateString domain ++ "';a='" ++ at' ++ "';n='" ++
172+ obfuscateString name ++ "';e=n+a+h;\n" ++
173+ "document.write('<a h'+'ref'+'=\"ma'+'ilto'+':'+e+'\">'+" ++
174+ linkText ++ "+'<\\/'+'a'+'>');\n// -->\n")) +++
175+ noscript (primHtml $ obfuscateString altText)
176+ _ -> anchor ! [href src] $ primHtml text -- malformed email
175177
176178-- | Obfuscate character as entity.
177179obfuscateChar :: Char -> String
178180obfuscateChar char =
179− let num = ord char in
180− let numstr = if even num then (show num) else ("x" ++ (showHex num "")) in
181− "&#" ++ numstr ++ ";"
181+ let num = ord char
182+ numstr = if even num then show num else "x" ++ showHex num ""
183+ in "&#" ++ numstr ++ ";"
182184
183185-- | Obfuscate string using entities.
184186obfuscateString :: String -> String
185−obfuscateString = (concatMap obfuscateChar) . decodeEntities
187+obfuscateString = concatMap obfuscateChar . decodeCharacterReferences
186188
187189-- | True if character is a punctuation character (unicode).
188190isPunctuation :: Char -> Bool
189191isPunctuation c =
190− let c' = ord c in
191− if (c `elem` "!\"'()*,-./:;<>?[\\]`{|}~") || (c' >= 0x2000 && c' <= 0x206F) ||
192− (c' >= 0xE000 && c' <= 0xE0FF)
193− then True
194− else False
192+ let c' = ord c
193+ in if c `elem` "!\"'()*,-./:;<>?[\\]`{|}~" || c' >= 0x2000 && c' <= 0x206F ||
194+ c' >= 0xE000 && c' <= 0xE0FF
195+ then True
196+ else False
195197
196198-- | Add CSS for document header.
197199addToCSS :: String -> State WriterState ()
198200addToCSS item = do
199201 st <- get
200202 let current = stCSS st
201− put $ st {stCSS = (S.insert item current)}
203+ put $ st {stCSS = S.insert item current}
202204
203205-- | Convert Pandoc inline list to plain text identifier.
204206inlineListToIdentifier :: [Inline] -> String
@@ -206,27 +208,26 @@ inlineListToIdentifier [] = ""
206208inlineListToIdentifier (x:xs) =
207209 xAsText ++ inlineListToIdentifier xs
208210 where xAsText = case x of
209− Str s -> filter
210− (\c -> (c == '-') || not (isPunctuation c)) $
211− concat $ intersperse "-" $ words $ map toLower s
212− Emph lst -> inlineListToIdentifier lst
213− Strikeout lst -> inlineListToIdentifier lst
214− Superscript lst -> inlineListToIdentifier lst
215− Subscript lst -> inlineListToIdentifier lst
216− Strong lst -> inlineListToIdentifier lst
217− Quoted _ lst -> inlineListToIdentifier lst
218− Code s -> s
219− Space -> "-"
220− EmDash -> "-"
221− EnDash -> "-"
222− Apostrophe -> ""
223− Ellipses -> ""
224− LineBreak -> "-"
225− TeX _ -> ""
226− HtmlInline _ -> ""
227− Link lst _ -> inlineListToIdentifier lst
228− Image lst _ -> inlineListToIdentifier lst
229− Note _ -> ""
211+ Str s -> filter (\c -> c == '-' || not (isPunctuation c)) $
212+ concat $ intersperse "-" $ words $ map toLower s
213+ Emph lst -> inlineListToIdentifier lst
214+ Strikeout lst -> inlineListToIdentifier lst
215+ Superscript lst -> inlineListToIdentifier lst
216+ Subscript lst -> inlineListToIdentifier lst
217+ Strong lst -> inlineListToIdentifier lst
218+ Quoted _ lst -> inlineListToIdentifier lst
219+ Code s -> s
220+ Space -> "-"
221+ EmDash -> "-"
222+ EnDash -> "-"
223+ Apostrophe -> ""
224+ Ellipses -> ""
225+ LineBreak -> "-"
226+ TeX _ -> ""
227+ HtmlInline _ -> ""
228+ Link lst _ -> inlineListToIdentifier lst
229+ Image lst _ -> inlineListToIdentifier lst
230+ Note _ -> ""
230231
231232-- | Return unique identifiers for list of inline lists.
232233uniqueIdentifiers :: [[Inline]] -> [String]
@@ -236,102 +237,99 @@ uniqueIdentifiers ls =
236237 matches = length $ filter (== new) nonuniqueIds
237238 new' = new ++ if matches > 0 then ("-" ++ show matches) else ""
238239 in (new:nonuniqueIds, new':uniqueIds)
239− in reverse $ snd (foldl addIdentifier ([],[]) $ ls)
240+ in reverse $ snd $ foldl addIdentifier ([],[]) ls
240241
241242-- | Convert Pandoc block element to HTML.
242243blockToHtml :: WriterOptions -> Block -> State WriterState Html
243−blockToHtml opts block =
244− case block of
245− (Null) -> return $ noHtml
246− (Plain lst) -> inlineListToHtml opts lst
247− (Para lst) -> inlineListToHtml opts lst >>= (return . paragraph)
248− (RawHtml str) -> return $ primHtml str
249− (HorizontalRule) -> return $ hr
250− (CodeBlock str) -> return $ pre $ thecode << (str ++ "\n")
244+blockToHtml opts Null = return $ noHtml
245+blockToHtml opts (Plain lst) = inlineListToHtml opts lst
246+blockToHtml opts (Para lst) = inlineListToHtml opts lst >>= (return . paragraph)
247+blockToHtml opts (RawHtml str) = return $ primHtml str
248+blockToHtml opts (HorizontalRule) = return $ hr
249+blockToHtml opts (CodeBlock str) = return $ pre $ thecode << (str ++ "\n")
251250 -- the final \n for consistency with Markdown.pl
252− (BlockQuote blocks) -> -- in S5, treat list in blockquote specially
253− -- if default is incremental, make it nonincremental;
254− -- otherwise incremental
255− if writerS5 opts
256− then let inc = not (writerIncremental opts) in
257− case blocks of
258− [BulletList lst] -> blockToHtml (opts {writerIncremental = inc})
259− (BulletList lst)
260− [OrderedList attribs lst] ->
261− blockToHtml (opts {writerIncremental = inc})
262− (OrderedList attribs lst)
263− otherwise -> blockListToHtml opts blocks >>=
264− (return . blockquote)
265− else blockListToHtml opts blocks >>= (return . blockquote)
266− (Header level lst) -> do contents <- inlineListToHtml opts lst
267− st <- get
268− let ids = stIds st
269− let (id, rest) = if null ids
270− then ("", [])
271− else (head ids, tail ids)
272− put $ st {stIds = rest}
273− let attribs = [identifier id]
274− let headerHtml = case level of
275− 1 -> h1 contents ! attribs
276− 2 -> h2 contents ! attribs
277− 3 -> h3 contents ! attribs
278− 4 -> h4 contents ! attribs
279− 5 -> h5 contents ! attribs
280− 6 -> h6 contents ! attribs
281− _ -> paragraph contents ! attribs
282− let headerHtml' = if writerTableOfContents opts
283− then anchor ! [href ("#TOC-" ++ id)] $
284− headerHtml
285− else headerHtml
286− return headerHtml'
287− (BulletList lst) -> do contents <- mapM (blockListToHtml opts) lst
288− let attribs = if writerIncremental opts
289− then [theclass "incremental"]
290− else []
291− return $ unordList ! attribs $ contents
292− (OrderedList (startnum, numstyle, _) lst) -> do
293− contents <- mapM (blockListToHtml opts) lst
294− let numstyle' = camelCaseToHyphenated $ show numstyle
295− let attribs = (if writerIncremental opts
296− then [theclass "incremental"]
297− else []) ++
298− (if startnum /= 1
299− then [start startnum]
300− else []) ++
301− (if numstyle /= DefaultStyle
302− then [theclass numstyle']
303− else [])
304− if numstyle /= DefaultStyle
305− then addToCSS $ "ol." ++ numstyle' ++
306− " { list-style-type: " ++
307− numstyle' ++ "; }"
308− else return ()
309− return $ ordList ! attribs $ contents
310− (DefinitionList lst) -> do contents <- mapM (\(term, def) ->
311− do term' <- inlineListToHtml opts term
312− def' <- blockListToHtml opts def
313− return $ (term', def'))
314− lst
315− let attribs = if writerIncremental opts
316− then [theclass "incremental"]
317− else []
318− return $ defList ! attribs $ contents
319− (Table capt aligns widths headers rows) ->
320− do let alignStrings = map alignmentToString aligns
321− captionDoc <- if null capt
322− then return noHtml
323− else inlineListToHtml opts capt >>=
324− (return . caption)
325− colHeads <- colHeadsToHtml opts alignStrings
326− widths headers
327− rows' <- mapM (tableRowToHtml opts alignStrings) rows
328− return $ table $ captionDoc +++ colHeads +++ rows'
251+blockToHtml opts (BlockQuote blocks) =
252+ -- in S5, treat list in blockquote specially
253+ -- if default is incremental, make it nonincremental;
254+ -- otherwise incremental
255+ if writerS5 opts
256+ then let inc = not (writerIncremental opts) in
257+ case blocks of
258+ [BulletList lst] -> blockToHtml (opts {writerIncremental = inc})
259+ (BulletList lst)
260+ [OrderedList attribs lst] ->
261+ blockToHtml (opts {writerIncremental = inc})
262+ (OrderedList attribs lst)
263+ otherwise -> blockListToHtml opts blocks >>=
264+ (return . blockquote)
265+ else blockListToHtml opts blocks >>= (return . blockquote)
266+blockToHtml opts (Header level lst) = do
267+ contents <- inlineListToHtml opts lst
268+ st <- get
269+ let ids = stIds st
270+ let (id, rest) = if null ids
271+ then ("", [])
272+ else (head ids, tail ids)
273+ put $ st {stIds = rest}
274+ let attribs = [identifier id]
275+ let headerHtml = case level of
276+ 1 -> h1 contents ! attribs
277+ 2 -> h2 contents ! attribs
278+ 3 -> h3 contents ! attribs
279+ 4 -> h4 contents ! attribs
280+ 5 -> h5 contents ! attribs
281+ 6 -> h6 contents ! attribs
282+ _ -> paragraph contents ! attribs
283+ return $ if writerTableOfContents opts
284+ then anchor ! [href ("#TOC-" ++ id)] $ headerHtml
285+ else headerHtml
286+blockToHtml opts (BulletList lst) = do
287+ contents <- mapM (blockListToHtml opts) lst
288+ let attribs = if writerIncremental opts
289+ then [theclass "incremental"]
290+ else []
291+ return $ unordList ! attribs $ contents
292+blockToHtml opts (OrderedList (startnum, numstyle, _) lst) = do
293+ contents <- mapM (blockListToHtml opts) lst
294+ let numstyle' = camelCaseToHyphenated $ show numstyle
295+ let attribs = (if writerIncremental opts
296+ then [theclass "incremental"]
297+ else []) ++
298+ (if startnum /= 1
299+ then [start startnum]
300+ else []) ++
301+ (if numstyle /= DefaultStyle
302+ then [theclass numstyle']
303+ else [])
304+ if numstyle /= DefaultStyle
305+ then addToCSS $ "ol." ++ numstyle' ++
306+ " { list-style-type: " ++
307+ numstyle' ++ "; }"
308+ else return ()
309+ return $ ordList ! attribs $ contents
310+blockToHtml opts (DefinitionList lst) = do
311+ contents <- mapM (\(term, def) -> do term' <- inlineListToHtml opts term
312+ def' <- blockListToHtml opts def
313+ return $ (term', def')) lst
314+ let attribs = if writerIncremental opts
315+ then [theclass "incremental"]
316+ else []
317+ return $ defList ! attribs $ contents
318+blockToHtml opts (Table capt aligns widths headers rows) = do
319+ let alignStrings = map alignmentToString aligns
320+ captionDoc <- if null capt
321+ then return noHtml
322+ else inlineListToHtml opts capt >>= return . caption
323+ colHeads <- colHeadsToHtml opts alignStrings
324+ widths headers
325+ rows' <- mapM (tableRowToHtml opts alignStrings) rows
326+ return $ table $ captionDoc +++ colHeads +++ rows'
329327
330−colHeadsToHtml opts alignStrings widths headers =
331− do heads <- sequence $ zipWith3
332− (\align width item -> tableItemToHtml opts th align width item)
333− alignStrings widths headers
334− return $ tr $ toHtmlFromList heads
328+colHeadsToHtml opts alignStrings widths headers = do
329+ heads <- sequence $ zipWith3
330+ (\align width item -> tableItemToHtml opts th align width item)
331+ alignStrings widths headers
332+ return $ tr $ toHtmlFromList heads
335333
336334alignmentToString alignment = case alignment of
337335 AlignLeft -> "left"
@@ -339,24 +337,27 @@ alignmentToString alignment = case alignment of
339337 AlignCenter -> "center"
340338 AlignDefault -> "left"
341339
342−tableRowToHtml opts aligns cols =
343− do contents <- sequence $ zipWith3 (tableItemToHtml opts td) aligns (repeat 0) cols
344− return $ tr $ toHtmlFromList contents
340+tableRowToHtml opts aligns cols =
341+ (sequence $ zipWith3 (tableItemToHtml opts td) aligns (repeat 0) cols) >>=
342+ return . tr . toHtmlFromList
345343
346−tableItemToHtml opts tag align' width item =
347− do contents <- blockListToHtml opts item
348− let attrib = [align align'] ++
349− if (width /= 0)
350− then [thestyle ("{width: " ++ show (truncate (100*width)) ++ "%;}")]
351− else []
352− return $ tag ! attrib $ contents
344+tableItemToHtml opts tag align' width item = do
345+ contents <- blockListToHtml opts item
346+ let attrib = [align align'] ++
347+ if width /= 0
348+ then [thestyle ("{width: " ++ show (truncate (100*width)) ++
349+ "%;}")]
350+ else []
351+ return $ tag ! attrib $ contents
353352
354353blockListToHtml :: WriterOptions -> [Block] -> State WriterState Html
355−blockListToHtml opts lst = mapM (blockToHtml opts) lst >>= (return . toHtmlFromList)
354+blockListToHtml opts lst =
355+ mapM (blockToHtml opts) lst >>= return . toHtmlFromList
356356
357357-- | Convert list of Pandoc inline elements to HTML.
358358inlineListToHtml :: WriterOptions -> [Inline] -> State WriterState Html
359−inlineListToHtml opts lst = mapM (inlineToHtml opts) lst >>= (return . toHtmlFromList)
359+inlineListToHtml opts lst =
360+ mapM (inlineToHtml opts) lst >>= return . toHtmlFromList
360361
361362-- | Convert Pandoc inline element to HTML.
362363inlineToHtml :: WriterOptions -> Inline -> State WriterState Html
@@ -369,52 +370,58 @@ inlineToHtml opts inline =
369370 (EnDash) -> return $ primHtmlChar "ndash"
370371 (Ellipses) -> return $ primHtmlChar "hellip"
371372 (Apostrophe) -> return $ primHtmlChar "rsquo"
372− (Emph lst) -> inlineListToHtml opts lst >>= (return . emphasize)
373− (Strong lst) -> inlineListToHtml opts lst >>= (return . strong)
373+ (Emph lst) -> inlineListToHtml opts lst >>= return . emphasize
374+ (Strong lst) -> inlineListToHtml opts lst >>= return . strong
374375 (Code str) -> return $ thecode << str
375− (Strikeout lst) -> addToCSS ".strikeout { text-decoration: line-through; }" >>
376+ (Strikeout lst) -> addToCSS
377+ ".strikeout { text-decoration: line-through; }" >>
376378 inlineListToHtml opts lst >>=
377− (return . (thespan ! [theclass "strikeout"]))
378− (Superscript lst) -> inlineListToHtml opts lst >>= (return . sup)
379− (Subscript lst) -> inlineListToHtml opts lst >>= (return . sub)
379+ return . (thespan ! [theclass "strikeout"])
380+ (Superscript lst) -> inlineListToHtml opts lst >>= return . sup
381+ (Subscript lst) -> inlineListToHtml opts lst >>= return . sub
380382 (Quoted quoteType lst) ->
381383 let (leftQuote, rightQuote) = case quoteType of
382384 SingleQuote -> (primHtmlChar "lsquo",
383385 primHtmlChar "rsquo")
384386 DoubleQuote -> (primHtmlChar "ldquo",
385− primHtmlChar "rdquo") in
386− do contents <- inlineListToHtml opts lst
387− return $ leftQuote +++ contents +++ rightQuote
388− (TeX str) -> do if writerUseASCIIMathML opts
389− then modify (\st -> st {stMath = True})
390− else return ()
391− return $ stringToHtml str
387+ primHtmlChar "rdquo")
388+ in do contents <- inlineListToHtml opts lst
389+ return $ leftQuote +++ contents +++ rightQuote
390+ (TeX str) -> (if writerUseASCIIMathML opts
391+ then modify (\st -> st {stMath = True})
392+ else return ()) >> return (stringToHtml str)
392393 (HtmlInline str) -> return $ primHtml str
393394 (Link [Code str] (src,tit)) | "mailto:" `isPrefixOf` src ->
394− do return $ obfuscateLink opts str src
395− (Link txt (src,tit)) | "mailto:" `isPrefixOf` src ->
396− do linkText <- inlineListToHtml opts txt
397− return $ obfuscateLink opts (show linkText) src
398− (Link txt (src,tit)) ->
399− do linkText <- inlineListToHtml opts txt
400− return $ anchor ! ([href src] ++
401− if null tit then [] else [title tit]) $ linkText
402− (Image txt (source,tit)) ->
403− do alternate <- inlineListToHtml opts txt
404− let alternate' = renderHtmlFragment alternate
405− let attributes = [src source, title tit] ++
406− if null txt then [] else [alt alternate']
407− return $ image ! attributes
408− -- note: null title included, as in Markdown.pl
409− (Note contents) -> do st <- get
410− let notes = stNotes st
411− let number = (length notes) + 1
412− let ref = show number
413− htmlContents <- blockListToNote opts ref contents
414− put $ st {stNotes = (htmlContents:notes)} -- push contents onto front of notes
415− return $ anchor ! [href ("#fn" ++ ref),
416− theclass "footnoteRef",
417− identifier ("fnref" ++ ref)] << sup << ref
395+ return $ obfuscateLink opts str src
396+ (Link txt (src,tit)) | "mailto:" `isPrefixOf` src -> do
397+ linkText <- inlineListToHtml opts txt
398+ return $ obfuscateLink opts (show linkText) src
399+ (Link txt (src,tit)) -> do
400+ linkText <- inlineListToHtml opts txt
401+ return $ anchor ! ([href src] ++
402+ if null tit then [] else [title tit]) $
403+ linkText
404+ (Image txt (source,tit)) -> do
405+ alternate <- inlineListToHtml opts txt
406+ let alternate' = renderHtmlFragment alternate
407+ let attributes = [src source, title tit] ++
408+ if null txt
409+ then []
410+ else [alt alternate']
411+ return $ image ! attributes
412+ -- note: null title included, as in Markdown.pl
413+ (Note contents) -> do
414+ st <- get
415+ let notes = stNotes st
416+ let number = (length notes) + 1
417+ let ref = show number
418+ htmlContents <- blockListToNote opts ref contents
419+ -- push contents onto front of notes
420+ put $ st {stNotes = (htmlContents:notes)}
421+ return $ anchor ! [href ("#fn" ++ ref),
422+ theclass "footnoteRef",
423+ identifier ("fnref" ++ ref)] <<
424+ sup << ref
418425
419426blockListToNote :: WriterOptions -> String -> [Block] -> State WriterState Html
420427blockListToNote opts ref blocks =
@@ -434,6 +441,6 @@ blockListToNote opts ref blocks =
434441 [Plain (lst ++ backlink)]
435442 _ -> otherBlocks ++ [lastBlock,
436443 Plain backlink]
437− in do contents <- blockListToHtml opts blocks'
438− return $ li ! [identifier ("fn" ++ ref)] $ contents
444+ in do contents <- blockListToHtml opts blocks'
445+ return $ li ! [identifier ("fn" ++ ref)] $ contents
439446
src/Text/Pandoc/Writers/LaTeX.hs+21−24
@@ -27,16 +27,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2727
2828Conversion of 'Pandoc' format into LaTeX.
2929-}
30−module Text.Pandoc.Writers.LaTeX (
31− writeLaTeX
32− ) where
30+module Text.Pandoc.Writers.LaTeX ( writeLaTeX ) where
3331import Text.Pandoc.Definition
3432import Text.Pandoc.Shared
3533import Text.Printf ( printf )
3634import Data.List ( (\\), isInfixOf )
35+import Data.Char ( toLower )
3736import qualified Data.Set as S
3837import Control.Monad.State
39−import Data.Char ( toLower )
4038
4139data WriterState =
4240 WriterState { stIncludes :: S.Set String -- strings to include in header
@@ -77,16 +75,16 @@ latexHeader :: WriterOptions -- ^ Options, including LaTeX header
7775 -> Meta -- ^ Meta with bibliographic information
7876 -> State WriterState String
7977latexHeader options (Meta title authors date) = do
80− titletext <- if null title
81− then return ""
82− else do title' <- inlineListToLaTeX title
83− return $ "\\title{" ++ title' ++ "}\n"
84− extras <- get >>= (return . unlines . S.toList. stIncludes)
78+ titletext <- if null title
79+ then return ""
80+ else do title' <- inlineListToLaTeX title
81+ return $ "\\title{" ++ title' ++ "}\n"
82+ extras <- get >>= (return . unlines . S.toList. stIncludes)
8583 let verbatim = if "\\usepackage{fancyvrb}" `isInfixOf` extras
8684 then "\\VerbatimFootnotes % allows verbatim text in footnotes\n"
8785 else ""
88− let authorstext = "\\author{" ++ (joinWithSep "\\\\"
89− (map stringToLaTeX authors)) ++ "}\n"
86+ let authorstext = "\\author{" ++
87+ joinWithSep "\\\\" (map stringToLaTeX authors) ++ "}\n"
9088 let datetext = if date == ""
9189 then ""
9290 else "\\date{" ++ stringToLaTeX date ++ "}\n"
@@ -124,8 +122,8 @@ deVerb (other:rest) = other:(deVerb rest)
124122blockToLaTeX :: Block -- ^ Block to convert
125123 -> State WriterState String
126124blockToLaTeX Null = return ""
127−blockToLaTeX (Plain lst) = (inlineListToLaTeX lst) >>= (return . (++ "\n"))
128−blockToLaTeX (Para lst) = (inlineListToLaTeX lst) >>= (return . (++ "\n\n"))
125+blockToLaTeX (Plain lst) = inlineListToLaTeX lst >>= return . (++ "\n")
126+blockToLaTeX (Para lst) = inlineListToLaTeX lst >>= return . (++ "\n\n")
129127blockToLaTeX (BlockQuote lst) = do
130128 contents <- blockListToLaTeX lst
131129 return $ "\\begin{quote}\n" ++ contents ++ "\\end{quote}\n"
@@ -184,22 +182,22 @@ blockToLaTeX (Table caption aligns widths heads rows) = do
184182 colWidths aligns
185183 let tableBody = "\\begin{tabular}{" ++ colDescriptors ++ "}\n" ++
186184 headers ++ "\\hline\n" ++ concat rows' ++ "\\end{tabular}\n"
187− let centered str = "\\begin{center}\n" ++ str ++ "\\end{center}\n"
185+ let centered str = "\\begin{center}\n" ++ str ++ "\\end{center}\n"
188186 addToHeader "\\usepackage{array}\n\
189− \% This is needed because raggedright in table elements redefines \\\\:\n\
190− \\\newcommand{\\PreserveBackslash}[1]{\\let\\temp=\\\\#1\\let\\\\=\\temp}\n\
191− \\\let\\PBS=\\PreserveBackslash"
187+ \% This is needed because raggedright in table elements redefines \\\\:\n\
188+ \\\newcommand{\\PreserveBackslash}[1]{\\let\\temp=\\\\#1\\let\\\\=\\temp}\n\
189+ \\\let\\PBS=\\PreserveBackslash"
192190 return $ if null captionText
193191 then centered tableBody ++ "\n"
194− else "\\begin{table}[h]\n" ++ centered tableBody ++ "\\caption{" ++
195− captionText ++ "}\n" ++ "\\end{table}\n\n"
192+ else "\\begin{table}[h]\n" ++ centered tableBody ++
193+ "\\caption{" ++ captionText ++ "}\n" ++ "\\end{table}\n\n"
196194
197−blockListToLaTeX lst = mapM blockToLaTeX lst >>= (return . concat)
195+blockListToLaTeX lst = mapM blockToLaTeX lst >>= return . concat
198196
199197tableRowToLaTeX cols =
200− mapM blockListToLaTeX cols >>= (return . (++ "\\\\\n") . (joinWithSep " & "))
198+ mapM blockListToLaTeX cols >>= return . (++ "\\\\\n") . (joinWithSep " & ")
201199
202−listItemToLaTeX lst = blockListToLaTeX lst >>= (return . ("\\item "++))
200+listItemToLaTeX lst = blockListToLaTeX lst >>= return . ("\\item "++)
203201
204202defListItemToLaTeX (term, def) = do
205203 term' <- inlineListToLaTeX $ deVerb term
@@ -209,8 +207,7 @@ defListItemToLaTeX (term, def) = do
209207-- | Convert list of inline elements to LaTeX.
210208inlineListToLaTeX :: [Inline] -- ^ Inlines to convert
211209 -> State WriterState String
212−inlineListToLaTeX lst =
213− mapM inlineToLaTeX lst >>= (return . concat)
210+inlineListToLaTeX lst = mapM inlineToLaTeX lst >>= return . concat
214211
215212isQuoted :: Inline -> Bool
216213isQuoted (Quoted _ _) = True
src/Text/Pandoc/Writers/Man.hs+10−16
@@ -28,14 +28,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2828Conversion of 'Pandoc' documents to groff man page format.
2929
3030-}
31−module Text.Pandoc.Writers.Man (
32− writeMan
33− ) where
31+module Text.Pandoc.Writers.Man ( writeMan) where
3432import Text.Pandoc.Definition
3533import Text.Pandoc.Shared
3634import Text.Printf ( printf )
37−import Data.Char ( toUpper )
38−import Data.List ( group, isPrefixOf, drop, find, nub, intersperse )
35+import Data.List ( isPrefixOf, drop, nub, intersperse )
3936import Text.PrettyPrint.HughesPJ hiding ( Str )
4037import Control.Monad.State
4138
@@ -45,16 +42,15 @@ type WriterState = (Notes, Preprocessors)
4542
4643-- | Convert Pandoc to Man.
4744writeMan :: WriterOptions -> Pandoc -> String
48−writeMan opts document =
49− render $ evalState (pandocToMan opts document) ([],[])
45+writeMan opts document = render $ evalState (pandocToMan opts document) ([],[])
5046
5147-- | Return groff man representation of document.
5248pandocToMan :: WriterOptions -> Pandoc -> State WriterState Doc
5349pandocToMan opts (Pandoc meta blocks) = do
5450 let before = writerIncludeBefore opts
5551 let after = writerIncludeAfter opts
56− before' = if null before then empty else text before
57− after' = if null after then empty else text after
52+ let before' = if null before then empty else text before
53+ let after' = if null after then empty else text after
5854 (head, foot) <- metaToMan opts meta
5955 body <- blockListToMan opts blocks
6056 (notes, preprocessors) <- get
@@ -84,8 +80,8 @@ metaToMan options (Meta title authors date) = do
8480 1 -> text ".SH AUTHOR" $$ (text $ joinWithSep ", " authors)
8581 2 -> text ".SH AUTHORS" $$ (text $ joinWithSep ", " authors)
8682 return $ if writerStandalone options
87− then (head, foot)
88− else (empty, empty)
83+ then (head, foot)
84+ else (empty, empty)
8985
9086-- | Return man representation of notes.
9187notesToMan :: WriterOptions -> [[Block]] -> State WriterState Doc
@@ -93,7 +89,7 @@ notesToMan opts notes =
9389 if null notes
9490 then return empty
9591 else mapM (\(num, note) -> noteToMan opts num note) (zip [1..] notes) >>=
96− (return . (text ".SH NOTES" $$) . vcat)
92+ return . (text ".SH NOTES" $$) . vcat
9793
9894-- | Return man representation of a note.
9995noteToMan :: WriterOptions -> Int -> [Block] -> State WriterState Doc
@@ -110,8 +106,7 @@ wrappedMan opts sect = do
110106
111107-- | Association list of characters to escape.
112108manEscapes :: [(Char, String)]
113−manEscapes = [('\160', "\\ "), ('\'', "\\[aq]")] ++
114− backslashEscapes "\".@\\"
109+manEscapes = [('\160', "\\ "), ('\'', "\\[aq]")] ++ backslashEscapes "\".@\\"
115110
116111-- | Escape special characters for Man.
117112escapeString :: String -> String
@@ -140,8 +135,7 @@ blockToMan opts (Header level inlines) = do
140135 return $ text heading <> contents
141136blockToMan opts (CodeBlock str) = return $
142137 text ".PP" $$ text "\\f[CR]" $$
143− text ((unlines . map (" " ++) . lines) (escapeCode str)) <>
144− text "\\f[]"
138+ text ((unlines . map (" " ++) . lines) (escapeCode str)) <> text "\\f[]"
145139blockToMan opts (BlockQuote blocks) = do
146140 contents <- blockListToMan opts blocks
147141 return $ text ".RS" $$ contents $$ text ".RE"
src/Text/Pandoc/Writers/Markdown.hs+35−40
@@ -29,9 +29,7 @@ Conversion of 'Pandoc' documents to markdown-formatted plain text.
2929
3030Markdown: <http://daringfireball.net/projects/markdown/>
3131-}
32−module Text.Pandoc.Writers.Markdown (
33− writeMarkdown
34− ) where
32+module Text.Pandoc.Writers.Markdown ( writeMarkdown) where
3533import Text.Pandoc.Definition
3634import Text.Pandoc.Shared
3735import Text.Pandoc.Blocks
@@ -53,10 +51,10 @@ pandocToMarkdown :: WriterOptions -> Pandoc -> State WriterState Doc
5351pandocToMarkdown opts (Pandoc meta blocks) = do
5452 let before = writerIncludeBefore opts
5553 let after = writerIncludeAfter opts
56− before' = if null before then empty else text before
57− after' = if null after then empty else text after
54+ let before' = if null before then empty else text before
55+ let after' = if null after then empty else text after
5856 metaBlock <- metaToMarkdown opts meta
59− let head = if (writerStandalone opts)
57+ let head = if writerStandalone opts
6058 then metaBlock $+$ text (writerHeader opts)
6159 else empty
6260 let headerBlocks = filter isHeaderBlock blocks
@@ -73,8 +71,7 @@ pandocToMarkdown opts (Pandoc meta blocks) = do
7371
7472-- | Return markdown representation of reference key table.
7573keyTableToMarkdown :: WriterOptions -> KeyTable -> State WriterState Doc
76−keyTableToMarkdown opts refs =
77− mapM (keyToMarkdown opts) refs >>= (return . vcat)
74+keyTableToMarkdown opts refs = mapM (keyToMarkdown opts) refs >>= return . vcat
7875
7976-- | Return markdown representation of a reference key.
8077keyToMarkdown :: WriterOptions
@@ -90,7 +87,7 @@ keyToMarkdown opts (label, (src, tit)) = do
9087notesToMarkdown :: WriterOptions -> [[Block]] -> State WriterState Doc
9188notesToMarkdown opts notes =
9289 mapM (\(num, note) -> noteToMarkdown opts num note) (zip [1..] notes) >>=
93− (return . vcat)
90+ return . vcat
9491
9592-- | Return markdown representation of a note.
9693noteToMarkdown :: WriterOptions -> Int -> [Block] -> State WriterState Doc
@@ -143,8 +140,7 @@ tableOfContents opts headers =
143140-- | Converts an Element to a list item for a table of contents,
144141elementToListItem :: Element -> [Block]
145142elementToListItem (Blk _) = []
146−elementToListItem (Sec headerText subsecs) =
147− [Plain headerText] ++
143+elementToListItem (Sec headerText subsecs) = [Plain headerText] ++
148144 if null subsecs
149145 then []
150146 else [BulletList $ map elementToListItem subsecs]
@@ -184,9 +180,8 @@ blockToMarkdown opts (Table caption aligns widths headers rows) = do
184180 let makeRow = hsepBlocks . (zipWith alignHeader aligns) .
185181 (zipWith docToBlock widthsInChars)
186182 let head = makeRow headers'
187− rows' <- mapM (\row -> do
188− cols <- mapM (blockListToMarkdown opts) row
189− return $ makeRow cols) rows
183+ rows' <- mapM (\row -> do cols <- mapM (blockListToMarkdown opts) row
184+ return $ makeRow cols) rows
190185 let tableWidth = sum widthsInChars
191186 let maxRowHeight = maximum $ map heightOfBlock (head:rows')
192187 let isMultilineTable = maxRowHeight > 1
@@ -208,8 +203,7 @@ blockToMarkdown opts (OrderedList attribs items) = do
208203 let markers = orderedListMarkers attribs
209204 let markers' = map (\m -> if length m < 3
210205 then m ++ replicate (3 - length m) ' '
211− else m)
212− markers
206+ else m) markers
213207 contents <- mapM (\(item, num) -> orderedListItemToMarkdown opts item num) $
214208 zip markers' items
215209 return $ (vcat contents) <> text "\n"
@@ -241,8 +235,8 @@ definitionListItemToMarkdown opts (label, items) = do
241235 let tabStop = writerTabStop opts
242236 let leader = char ':'
243237 contents <- mapM (\item -> blockToMarkdown opts item >>=
244− (\txt -> return (leader $$ nest tabStop txt)))
245− items >>= (return . vcat)
238+ (\txt -> return (leader $$ nest tabStop txt)))
239+ items >>= return . vcat
246240 return $ labelText $+$ contents
247241
248242-- | Convert list of Pandoc block elements to markdown.
@@ -250,29 +244,30 @@ blockListToMarkdown :: WriterOptions -- ^ Options
250244 -> [Block] -- ^ List of block elements
251245 -> State WriterState Doc
252246blockListToMarkdown opts blocks =
253− mapM (blockToMarkdown opts) blocks >>= (return . vcat)
247+ mapM (blockToMarkdown opts) blocks >>= return . vcat
254248
255249-- | Get reference for target; if none exists, create unique one and return.
256250-- Prefer label if possible; otherwise, generate a unique key.
257251getReference :: [Inline] -> Target -> State WriterState [Inline]
258252getReference label (src, tit) = do
259− (_,refs) <- get
260− case find ((== (src, tit)) . snd) refs of
261− Just (ref, _) -> return ref
262− Nothing -> do
263− let label' = case find ((== label) . fst) refs of
264− Just _ -> -- label is used; generate numerical label
265− case find (\n -> not (any (== [Str (show n)])
266− (map fst refs))) [1..10000] of
267− Just x -> [Str (show x)]
268− Nothing -> error "no unique label"
269− Nothing -> label
270− modify (\(notes, refs) -> (notes, (label', (src,tit)):refs))
271− return label'
253+ (_,refs) <- get
254+ case find ((== (src, tit)) . snd) refs of
255+ Just (ref, _) -> return ref
256+ Nothing -> do
257+ let label' = case find ((== label) . fst) refs of
258+ Just _ -> -- label is used; generate numerical label
259+ case find (\n -> not (any (== [Str (show n)])
260+ (map fst refs))) [1..10000] of
261+ Just x -> [Str (show x)]
262+ Nothing -> error "no unique label"
263+ Nothing -> label
264+ modify (\(notes, refs) -> (notes, (label', (src,tit)):refs))
265+ return label'
272266
273267-- | Convert list of Pandoc inline elements to markdown.
274268inlineListToMarkdown :: WriterOptions -> [Inline] -> State WriterState Doc
275−inlineListToMarkdown opts lst = mapM (inlineToMarkdown opts) lst >>= (return . hcat)
269+inlineListToMarkdown opts lst =
270+ mapM (inlineToMarkdown opts) lst >>= return . hcat
276271
277272-- | Convert Pandoc inline element to markdown.
278273inlineToMarkdown :: WriterOptions -> Inline -> State WriterState Doc
@@ -327,13 +322,13 @@ inlineToMarkdown opts (Link txt (src, tit)) = do
327322 return $ if useAuto
328323 then char '<' <> text srcSuffix <> char '>'
329324 else if useRefLinks
330− then let first = char '[' <> linktext <> char ']'
331− second = if txt == ref
332− then text "[]"
333− else char '[' <> reftext <> char ']'
334− in first <> second
335− else char '[' <> linktext <> char ']' <>
336− char '(' <> text src <> linktitle <> char ')'
325+ then let first = char '[' <> linktext <> char ']'
326+ second = if txt == ref
327+ then text "[]"
328+ else char '[' <> reftext <> char ']'
329+ in first <> second
330+ else char '[' <> linktext <> char ']' <>
331+ char '(' <> text src <> linktitle <> char ')'
337332inlineToMarkdown opts (Image alternate (source, tit)) = do
338333 let txt = if (null alternate) || (alternate == [Str ""]) ||
339334 (alternate == [Str source]) -- to prevent autolinks
src/Text/Pandoc/Writers/RST.hs+19−27
@@ -29,13 +29,11 @@ Conversion of 'Pandoc' documents to reStructuredText.
2929
3030reStructuredText: <http://docutils.sourceforge.net/rst.html>
3131-}
32−module Text.Pandoc.Writers.RST (
33− writeRST
34− ) where
32+module Text.Pandoc.Writers.RST ( writeRST) where
3533import Text.Pandoc.Definition
3634import Text.Pandoc.Shared
3735import Text.Pandoc.Blocks
38−import Data.List ( group, isPrefixOf, drop, find, intersperse )
36+import Data.List ( isPrefixOf, isSuffixOf, drop, intersperse )
3937import Text.PrettyPrint.HughesPJ hiding ( Str )
4038import Control.Monad.State
4139
@@ -70,8 +68,7 @@ pandocToRST opts (Pandoc meta blocks) = do
7068
7169-- | Return RST representation of reference key table.
7270keyTableToRST :: WriterOptions -> KeyTable -> State WriterState Doc
73−keyTableToRST opts refs =
74− mapM (keyToRST opts) refs >>= (return . vcat)
71+keyTableToRST opts refs = mapM (keyToRST opts) refs >>= return . vcat
7572
7673-- | Return RST representation of a reference key.
7774keyToRST :: WriterOptions
@@ -85,7 +82,7 @@ keyToRST opts (label, (src, tit)) = do
8582notesToRST :: WriterOptions -> [[Block]] -> State WriterState Doc
8683notesToRST opts notes =
8784 mapM (\(num, note) -> noteToRST opts num note) (zip [1..] notes) >>=
88− (return . vcat)
85+ return . vcat
8986
9087-- | Return RST representation of a note.
9188noteToRST :: WriterOptions -> Int -> [Block] -> State WriterState Doc
@@ -96,8 +93,7 @@ noteToRST opts num note = do
9693
9794-- | Return RST representation of picture reference table.
9895pictTableToRST :: WriterOptions -> KeyTable -> State WriterState Doc
99−pictTableToRST opts refs =
100− mapM (pictToRST opts) refs >>= (return . vcat)
96+pictTableToRST opts refs = mapM (pictToRST opts) refs >>= return . vcat
10197
10298-- | Return RST representation of a picture substitution reference.
10399pictToRST :: WriterOptions
@@ -112,7 +108,7 @@ pictToRST opts (label, (src, _)) = do
112108wrappedRST :: WriterOptions -> [Inline] -> State WriterState Doc
113109wrappedRST opts inlines =
114110 mapM (wrappedRSTSection opts) (splitBy LineBreak inlines) >>=
115− (return . vcat)
111+ return . vcat
116112
117113wrappedRSTSection :: WriterOptions -> [Inline] -> State WriterState Doc
118114wrappedRSTSection opts sect = do
@@ -160,21 +156,19 @@ blockToRST :: WriterOptions -- ^ Options
160156blockToRST opts Null = return empty
161157blockToRST opts (Plain inlines) = wrappedRST opts inlines
162158blockToRST opts (Para [TeX str]) =
163− let str' = if (endsWith '\n' str) then (str ++ "\n") else (str ++ "\n\n") in
164− return $ hang (text "\n.. raw:: latex\n") 3
165− (vcat $ map text (lines str'))
159+ let str' = if "\n" `isSuffixOf` str then str ++ "\n" else str ++ "\n\n" in
160+ return $ hang (text "\n.. raw:: latex\n") 3 $ vcat $ map text (lines str')
166161blockToRST opts (Para inlines) = do
167162 contents <- wrappedRST opts inlines
168163 return $ contents <> text "\n"
169164blockToRST opts (RawHtml str) =
170− let str' = if (endsWith '\n' str) then (str ++ "\n") else (str ++ "\n\n") in
171− return $ hang (text "\n.. raw:: html\n") 3
172− (vcat $ map text (lines str'))
165+ let str' = if "\n" `isSuffixOf` str then str ++ "\n" else str ++ "\n\n" in
166+ return $ hang (text "\n.. raw:: html\n") 3 $ vcat $ map text (lines str')
173167blockToRST opts HorizontalRule = return $ text "--------------\n"
174168blockToRST opts (Header level inlines) = do
175169 contents <- inlineListToRST opts inlines
176170 let headerLength = length $ render contents
177− let headerChar = if (level > 5) then ' ' else "=-~^'" !! (level - 1)
171+ let headerChar = if level > 5 then ' ' else "=-~^'" !! (level - 1)
178172 let border = text $ replicate headerLength headerChar
179173 return $ contents $+$ border <> text "\n"
180174blockToRST opts (CodeBlock str) = return $ (text "::\n") $+$
@@ -200,11 +194,10 @@ blockToRST opts (Table caption aligns widths headers rows) = do
200194 beg = TextBlock 2 height (replicate height "| ")
201195 end = TextBlock 2 height (replicate height " |")
202196 middle = hcatBlocks $ intersperse sep blocks
203− let makeRow = hpipeBlocks . (zipWith docToBlock widthsInChars)
197+ let makeRow = hpipeBlocks . zipWith docToBlock widthsInChars
204198 let head = makeRow headers'
205− rows' <- mapM (\row -> do
206− cols <- mapM (blockListToRST opts) row
207− return $ makeRow cols) rows
199+ rows' <- mapM (\row -> do cols <- mapM (blockListToRST opts) row
200+ return $ makeRow cols) rows
208201 let tableWidth = sum widthsInChars
209202 let maxRowHeight = maximum $ map heightOfBlock (head:rows')
210203 let border ch = char '+' <> char ch <>
@@ -225,8 +218,7 @@ blockToRST opts (OrderedList (start, style, delim) items) = do
225218 (start, style, delim)
226219 let maxMarkerLength = maximum $ map length markers
227220 let markers' = map (\m -> let s = maxMarkerLength - length m
228− in m ++ replicate s ' ')
229− markers
221+ in m ++ replicate s ' ') markers
230222 contents <- mapM (\(item, num) -> orderedListItemToRST opts item num) $
231223 zip markers' items
232224 -- ensure that sublists have preceding blank line
@@ -262,11 +254,11 @@ blockListToRST :: WriterOptions -- ^ Options
262254 -> [Block] -- ^ List of block elements
263255 -> State WriterState Doc
264256blockListToRST opts blocks =
265− mapM (blockToRST opts) blocks >>= (return . vcat)
257+ mapM (blockToRST opts) blocks >>= return . vcat
266258
267259-- | Convert list of Pandoc inline elements to RST.
268260inlineListToRST :: WriterOptions -> [Inline] -> State WriterState Doc
269−inlineListToRST opts lst = mapM (inlineToRST opts) lst >>= (return . hcat)
261+inlineListToRST opts lst = mapM (inlineToRST opts) lst >>= return . hcat
270262
271263-- | Convert Pandoc inline element to RST.
272264inlineToRST :: WriterOptions -> Inline -> State WriterState Doc
@@ -319,8 +311,8 @@ inlineToRST opts (Link txt (src, tit)) = do
319311inlineToRST opts (Image alternate (source, tit)) = do
320312 (notes, refs, pics) <- get
321313 let labelsUsed = map fst pics
322− let txt = if (null alternate) || (alternate == [Str ""]) ||
323− (alternate `elem` labelsUsed)
314+ let txt = if null alternate || alternate == [Str ""] ||
315+ alternate `elem` labelsUsed
324316 then [Str $ "image" ++ show (length refs)]
325317 else alternate
326318 let pics' = if (txt, (source, tit)) `elem` pics
src/Text/Pandoc/Writers/RTF.hs+46−48
@@ -27,12 +27,12 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2727
2828Conversion of 'Pandoc' documents to RTF (rich text format).
2929-}
30−module Text.Pandoc.Writers.RTF ( writeRTF) where
30+module Text.Pandoc.Writers.RTF ( writeRTF ) where
3131import Text.Pandoc.Definition
3232import Text.Pandoc.Shared
3333import Text.Regex ( matchRegexAll, mkRegex )
3434import Data.List ( isSuffixOf )
35−import Data.Char ( ord, chr )
35+import Data.Char ( ord )
3636
3737-- | Convert Pandoc to a string in rich text format.
3838writeRTF :: WriterOptions -> Pandoc -> String
@@ -44,22 +44,22 @@ writeRTF options (Pandoc meta blocks) =
4444 then tableOfContents $ filter isHeaderBlock blocks
4545 else ""
4646 foot = if writerStandalone options then "\n}\n" else ""
47− body = (writerIncludeBefore options) ++
47+ body = writerIncludeBefore options ++
4848 concatMap (blockToRTF 0 AlignDefault) blocks ++
49− (writerIncludeAfter options) in
50− head ++ toc ++ body ++ foot
49+ writerIncludeAfter options
50+ in head ++ toc ++ body ++ foot
5151
5252-- | Construct table of contents from list of header blocks.
5353tableOfContents :: [Block] -> String
5454tableOfContents headers =
5555 let contentsTree = hierarchicalize headers
56− in concatMap (blockToRTF 0 AlignDefault) $ [Header 1 [Str "Contents"],
57− BulletList (map elementToListItem contentsTree)]
56+ in concatMap (blockToRTF 0 AlignDefault) $
57+ [Header 1 [Str "Contents"],
58+ BulletList (map elementToListItem contentsTree)]
5859
5960elementToListItem :: Element -> [Block]
6061elementToListItem (Blk _) = []
61−elementToListItem (Sec sectext subsecs) =
62− [Plain sectext] ++
62+elementToListItem (Sec sectext subsecs) = [Plain sectext] ++
6363 if null subsecs
6464 then []
6565 else [BulletList (map elementToListItem subsecs)]
@@ -67,10 +67,10 @@ elementToListItem (Sec sectext subsecs) =
6767-- | Convert unicode characters (> 127) into rich text format representation.
6868handleUnicode :: String -> String
6969handleUnicode [] = []
70−handleUnicode (c:cs) = if (ord c) > 127
71− then '\\':'u':(show (ord c)) ++ "?" ++
72− (handleUnicode cs)
73− else c:(handleUnicode cs)
70+handleUnicode (c:cs) =
71+ if ord c > 127
72+ then '\\':'u':(show (ord c)) ++ "?" ++ handleUnicode cs
73+ else c:(handleUnicode cs)
7474
7575-- | Escape special characters.
7676escapeSpecial :: String -> String
@@ -127,7 +127,7 @@ listIncrement = 360
127127
128128-- | Returns appropriate bullet list marker for indent level.
129129bulletMarker :: Int -> String
130−bulletMarker indent = case (indent `mod` 720) of
130+bulletMarker indent = case indent `mod` 720 of
131131 0 -> "\\bullet "
132132 otherwise -> "\\endash "
133133
@@ -135,7 +135,7 @@ bulletMarker indent = case (indent `mod` 720) of
135135orderedMarkers :: Int -> ListAttributes -> [String]
136136orderedMarkers indent (start, style, delim) =
137137 if style == DefaultStyle && delim == DefaultDelim
138− then case (indent `mod` 720) of
138+ then case indent `mod` 720 of
139139 0 -> orderedListMarkers (start, Decimal, Period)
140140 otherwise -> orderedListMarkers (start, LowerAlpha, Period)
141141 else orderedListMarkers (start, style, delim)
@@ -145,21 +145,21 @@ rtfHeader :: String -- ^ header text
145145 -> Meta -- ^ bibliographic information
146146 -> String
147147rtfHeader headerText (Meta title authors date) =
148− let titletext = if null title
148+ let titletext = if null title
149+ then ""
150+ else rtfPar 0 0 AlignCenter $
151+ "\\b \\fs36 " ++ inlineListToRTF title
152+ authorstext = if null authors
149153 then ""
150− else rtfPar 0 0 AlignCenter ("\\b \\fs36 " ++
151− 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)
154+ else rtfPar 0 0 AlignCenter (" " ++ (joinWithSep "\\" $
155+ map stringToRTF authors))
156+ datetext = if date == ""
160157 then ""
161− else rtfPar 0 0 AlignDefault "" in
162− headerText ++ titletext ++ authorstext ++ datetext ++ spacer
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
163163
164164-- | Convert Pandoc block element to RTF.
165165blockToRTF :: Int -- ^ indent level
@@ -168,31 +168,27 @@ blockToRTF :: Int -- ^ indent level
168168 -> String
169169blockToRTF _ _ Null = ""
170170blockToRTF indent alignment (Plain lst) =
171− rtfCompact indent 0 alignment (inlineListToRTF lst)
171+ rtfCompact indent 0 alignment $ inlineListToRTF lst
172172blockToRTF indent alignment (Para lst) =
173− rtfPar indent 0 alignment (inlineListToRTF lst)
173+ rtfPar indent 0 alignment $ inlineListToRTF lst
174174blockToRTF indent alignment (BlockQuote lst) =
175175 concatMap (blockToRTF (indent + indentIncrement) alignment) lst
176176blockToRTF indent _ (CodeBlock str) =
177177 rtfPar indent 0 AlignLeft ("\\f1 " ++ (codeStringToRTF str))
178178blockToRTF _ _ (RawHtml str) = ""
179−blockToRTF indent alignment (BulletList lst) =
180− spaceAtEnd $
179+blockToRTF indent alignment (BulletList lst) = spaceAtEnd $
181180 concatMap (listItemToRTF alignment indent (bulletMarker indent)) lst
182−blockToRTF indent alignment (OrderedList attribs lst) =
183− spaceAtEnd $ concat $
181+blockToRTF indent alignment (OrderedList attribs lst) = spaceAtEnd $ concat $
184182 zipWith (listItemToRTF alignment indent) (orderedMarkers indent attribs) lst
185−blockToRTF indent alignment (DefinitionList lst) =
186− spaceAtEnd $
183+blockToRTF indent alignment (DefinitionList lst) = spaceAtEnd $
187184 concatMap (definitionListItemToRTF alignment indent) lst
188185blockToRTF indent _ HorizontalRule =
189186 rtfPar indent 0 AlignCenter "\\emdash\\emdash\\emdash\\emdash\\emdash"
190−blockToRTF indent alignment (Header level lst) =
191− rtfPar indent 0 alignment ("\\b \\fs" ++ (show (40 - (level * 4))) ++ " " ++
192− (inlineListToRTF lst))
187+blockToRTF indent alignment (Header level lst) = rtfPar indent 0 alignment $
188+ "\\b \\fs" ++ (show (40 - (level * 4))) ++ " " ++ inlineListToRTF lst
193189blockToRTF indent alignment (Table caption aligns sizes headers rows) =
194− (tableRowToRTF True indent aligns sizes headers) ++ (concatMap
195− (tableRowToRTF False indent aligns sizes) rows) ++
190+ tableRowToRTF True indent aligns sizes headers ++
191+ concatMap (tableRowToRTF False indent aligns sizes) rows ++
196192 rtfPar indent 0 alignment (inlineListToRTF caption)
197193
198194tableRowToRTF :: Bool -> Int -> [Alignment] -> [Float] -> [[Block]] -> String
@@ -201,8 +197,10 @@ tableRowToRTF header indent aligns sizes cols =
201197 totalTwips = 6 * 1440 -- 6 inches
202198 rightEdges = tail $ scanl (\sofar new -> sofar + floor (new * totalTwips))
203199 0 sizes
204− cellDefs = map (\edge -> (if header then "\\clbrdrb\\brdrs"
205− else "") ++ "\\cellx" ++ show edge) rightEdges
200+ cellDefs = map (\edge -> (if header
201+ then "\\clbrdrb\\brdrs"
202+ else "") ++ "\\cellx" ++ show edge)
203+ rightEdges
206204 start = "{\n\\trowd \\trgaph120\n" ++ concat cellDefs ++ "\n" ++
207205 "\\trkeep\\intbl\n{\n"
208206 end = "}\n\\intbl\\row}\n"
@@ -234,11 +232,12 @@ listItemToRTF alignment indent marker list =
234232 let (first:rest) = map (blockToRTF (indent + listIncrement) alignment) list in
235233 -- insert the list marker into the (processed) first block
236234 let modFirst = case matchRegexAll (mkRegex "\\\\fi-?[0-9]+") first of
237− Just (before, matched, after, _) -> before ++ "\\fi" ++
238− show (0 - listIncrement) ++ " " ++ marker ++ "\\tx" ++
239− show listIncrement ++ "\\tab" ++ after
235+ Just (before, matched, after, _) ->
236+ before ++ "\\fi" ++ show (0 - listIncrement) ++
237+ " " ++ marker ++ "\\tx" ++
238+ show listIncrement ++ "\\tab" ++ after
240239 Nothing -> first in
241− modFirst ++ (concat rest)
240+ modFirst ++ concat rest
242241
243242-- | Convert definition list item (label, list of blocks) to RTF.
244243definitionListItemToRTF :: Alignment -- ^ alignment
@@ -285,4 +284,3 @@ inlineToRTF (Image alternate (source, tit)) =
285284inlineToRTF (Note contents) =
286285 "{\\super\\chftn}{\\*\\footnote\\chftn\\~\\plain\\pard " ++
287286 (concatMap (blockToRTF 0 AlignDefault) contents) ++ "}"
288−
src/templates/DefaultHeaders.hs+29−1
@@ -1,4 +1,32 @@
1−-- | Default headers for Pandoc writers.
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+-}
230module Text.Pandoc.Writers.DefaultHeaders (
331 defaultLaTeXHeader,
432 defaultConTeXtHeader,
src/templates/S5.hs+47−12
@@ -1,5 +1,33 @@
1−-- | Definitions for creation of S5 powerpoint-like HTML.
2−-- (See <http://meyerweb.com/eric/tools/s5/>.)
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+-}
331module Text.Pandoc.Writers.S5 (
432 -- * Strings
533 s5Meta,
@@ -60,13 +88,13 @@ layoutDiv :: [Inline] -- ^ Title of document (for header or footer)
6088 -> [Block] -- ^ List of block elements returned
6189layoutDiv 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")]
6290
63−presentationStart = (RawHtml "<div class=\"presentation\">\n\n")
91+presentationStart = RawHtml "<div class=\"presentation\">\n\n"
6492
65−presentationEnd = (RawHtml "</div>\n")
93+presentationEnd = RawHtml "</div>\n"
6694
67−slideStart = (RawHtml "<div class=\"slide\">\n")
95+slideStart = RawHtml "<div class=\"slide\">\n"
6896
69−slideEnd = (RawHtml "</div>\n")
97+slideEnd = RawHtml "</div>\n"
7098
7199-- | Returns 'True' if block is a Header 1.
72100isH1 :: Block -> Bool
@@ -84,15 +112,22 @@ insertSlides beginning blocks =
84112 beforeHead ++ [slideEnd]
85113 else
86114 if beginning then
87− beforeHead ++ slideStart:(head rest):(insertSlides False (tail rest))
115+ beforeHead ++
116+ slideStart:(head rest):(insertSlides False (tail rest))
88117 else
89− beforeHead ++ slideEnd:slideStart:(head rest):(insertSlides False (tail rest))
118+ beforeHead ++
119+ slideEnd:slideStart:(head rest):(insertSlides False (tail rest))
90120
91121-- | Insert blocks into 'Pandoc' for slide structure.
92122insertS5Structure :: Pandoc -> Pandoc
93123insertS5Structure (Pandoc meta []) = Pandoc meta []
94124insertS5Structure (Pandoc (Meta title authors date) blocks) =
95− let slides = insertSlides True blocks
96− firstSlide = if (not (null title)) then [slideStart, (Header 1 title), (Header 3 [Str (joinWithSep ", " authors)]), (Header 4 [Str date]), slideEnd] else [] in
97− let newBlocks = (layoutDiv title date) ++ presentationStart:firstSlide ++ slides ++ [presentationEnd] in
98− Pandoc (Meta title authors date) newBlocks
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