git.iko.soy/typeable/pandoc
on master help sign in
Major code cleanup in all modules. (Removed unneeded imports,
a8e2199fiddlosopher
pandoc.cabal+1−3
@@ -34,11 +34,9 @@ Description: Pandoc is a Haskell library for converting from one markup
34Build-Depends: base, parsec, xhtml, mtl, regex-compat
34Build-Depends: base, parsec, xhtml, mtl, regex-compat
35Hs-Source-Dirs: src
35Hs-Source-Dirs: src
36Exposed-Modules: Text.Pandoc,
36Exposed-Modules: Text.Pandoc,
37− Text.Pandoc.ASCIIMathML,
38 Text.Pandoc.Blocks,
37 Text.Pandoc.Blocks,
39 Text.Pandoc.Definition,
38 Text.Pandoc.Definition,
40− Text.Pandoc.Entities,
39+ Text.Pandoc.CharacterReferences,
41− Text.Pandoc.ParserCombinators,
42 Text.Pandoc.Shared,
40 Text.Pandoc.Shared,
43 Text.Pandoc.UTF8,
41 Text.Pandoc.UTF8,
44 Text.Pandoc.Readers.HTML,
42 Text.Pandoc.Readers.HTML,
src/Text/Pandoc.hs+2−2
@@ -39,8 +39,8 @@ inline links:
39>
39>
40> markdownToRST :: String -> String
40> markdownToRST :: String -> String
41> markdownToRST = toUTF8 .
41> markdownToRST = toUTF8 .
42−> (writeRST defaultWriterOptions {writerReferenceLinks = True}) .
42+> (writeRST defaultWriterOptions {writerReferenceLinks = True}) .
43−> (readMarkdown defaultParserState) . fromUTF8
43+> (readMarkdown defaultParserState) . fromUTF8
44>
44>
45> main = interact markdownToRST
45> main = interact markdownToRST
46
46
src/Text/Pandoc/Blocks.hs+15−11
@@ -43,9 +43,8 @@ module Text.Pandoc.Blocks
43 rightAlignBlock
43 rightAlignBlock
44 )
44 )
45where
45where
46−
47import Text.PrettyPrint
46import Text.PrettyPrint
48−import Data.List (transpose, intersperse)
47+import Data.List ( intersperse )
49
48
50-- | A fixed-width block of text. Parameters are width of block,
49-- | A fixed-width block of text. Parameters are width of block,
51-- height of block, and list of lines.
50-- height of block, and list of lines.
@@ -53,6 +52,17 @@ data TextBlock = TextBlock Int Int [String]
53instance Show TextBlock where
52instance Show TextBlock where
54 show x = show $ blockToDoc x
53 show x = show $ blockToDoc x
55
54
55+-- | Break lines in a list of lines so that none are greater than
56+-- a given width.
57+breakLines :: Int -- ^ Maximum length of lines.
58+ -> [String] -- ^ List of lines.
59+ -> [String]
60+breakLines width [] = []
61+breakLines width (l:ls) =
62+ if length l > width
63+ then (take width l):(breakLines width ((drop width l):ls))
64+ else l:(breakLines width ls)
65+
56-- | Convert a @Doc@ element into a @TextBlock@ with a specified width.
66-- | Convert a @Doc@ element into a @TextBlock@ with a specified width.
57docToBlock :: Int -- ^ Width of text block.
67docToBlock :: Int -- ^ Width of text block.
58 -> Doc -- ^ @Doc@ to convert.
68 -> Doc -- ^ @Doc@ to convert.
@@ -60,13 +70,8 @@ docToBlock :: Int -- ^ Width of text block.
60docToBlock width doc =
70docToBlock width doc =
61 let rendered = renderStyle (style {lineLength = width,
71 let rendered = renderStyle (style {lineLength = width,
62 ribbonsPerLine = 1}) doc
72 ribbonsPerLine = 1}) doc
63− lns = lines rendered
73+ lns = breakLines width $ 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'
74+ in TextBlock width (length lns) lns
70
75
71-- | Convert a @TextBlock@ to a @Doc@ element.
76-- | Convert a @TextBlock@ to a @Doc@ element.
72blockToDoc :: TextBlock -> Doc
77blockToDoc :: TextBlock -> Doc
@@ -116,8 +121,7 @@ isWhitespace x = x `elem` " \t"
116-- | Left-aligns the contents of a @TextBlock@ within the block.
121-- | Left-aligns the contents of a @TextBlock@ within the block.
117leftAlignBlock :: TextBlock -> TextBlock
122leftAlignBlock :: TextBlock -> TextBlock
118leftAlignBlock (TextBlock width height lns) =
123leftAlignBlock (TextBlock width height lns) =
119− TextBlock width height $
120− map (dropWhile isWhitespace) lns
124+ TextBlock width height $ map (dropWhile isWhitespace) lns
121
125
122-- | Right-aligns the contents of a @TextBlock@ within the block.
126-- | Right-aligns the contents of a @TextBlock@ within the block.
123rightAlignBlock :: TextBlock -> TextBlock
127rightAlignBlock :: 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
17-}
17-}
18
18
19{- |
19{- |
20− Module : Text.Pandoc.Entities
20+ Module : Text.Pandoc.CharacterReferences
21 Copyright : Copyright (C) 2006-7 John MacFarlane
21 Copyright : Copyright (C) 2006-7 John MacFarlane
22 License : GNU GPL, version 2 or above
22 License : GNU GPL, version 2 or above
23
23
@@ -25,37 +25,26 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 Stability : alpha
25 Stability : alpha
26 Portability : portable
26 Portability : portable
27
27
28−Functions for encoding unicode characters as entity references,
28+Functions for parsing character references.
29−and vice versa.
30-}
29-}
31−module Text.Pandoc.Entities (
30+module Text.Pandoc.CharacterReferences (
32− charToEntity,
31+ characterReference,
33− charToNumericalEntity,
32+ decodeCharacterReferences,
34− decodeEntities,
35− escapeCharForXML,
36− escapeStringForXML,
37− characterEntity
38 ) where
33 ) where
39−import Data.Char ( chr, ord )
34+import Data.Char ( chr )
40import Text.ParserCombinators.Parsec
35import Text.ParserCombinators.Parsec
41−import Data.Maybe ( fromMaybe )
42import qualified Data.Map as Map
36import qualified Data.Map as Map
43
37
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−
52-- | Parse character entity.
38-- | Parse character entity.
53−characterEntity :: GenParser Char st Char
39+characterReference :: GenParser Char st Char
54−characterEntity = namedEntity <|> hexEntity <|> decimalEntity <?> "character entity"
40+characterReference = characterEntity <|>
41+ hexadecimalCharacterReference <|>
42+ decimalCharacterReference <?>
43+ "character entity"
55
44
56-- | Parse character entity.
45-- | Parse character entity.
57−namedEntity :: GenParser Char st Char
46+characterEntity :: GenParser Char st Char
58−namedEntity = try $ do
47+characterEntity = try $ do
59 st <- char '&'
48 st <- char '&'
60 body <- many1 alphaNum
49 body <- many1 alphaNum
61 end <- char ';'
50 end <- char ';'
@@ -63,8 +52,8 @@ namedEntity = try $ do
63 return $ Map.findWithDefault '?' entity entityTable
52 return $ Map.findWithDefault '?' entity entityTable
64
53
65-- | Parse hexadecimal entity.
54-- | Parse hexadecimal entity.
66−hexEntity :: GenParser Char st Char
55+hexadecimalCharacterReference :: GenParser Char st Char
67−hexEntity = try $ do
56+hexadecimalCharacterReference = try $ do
68 st <- string "&#"
57 st <- string "&#"
69 hex <- oneOf "Xx"
58 hex <- oneOf "Xx"
70 body <- many1 (oneOf "0123456789ABCDEFabcdef")
59 body <- many1 (oneOf "0123456789ABCDEFabcdef")
@@ -72,49 +61,23 @@ hexEntity = try $ do
72 return $ chr $ read ('0':'x':body)
61 return $ chr $ read ('0':'x':body)
73
62
74-- | Parse decimal entity.
63-- | Parse decimal entity.
75−decimalEntity :: GenParser Char st Char
64+decimalCharacterReference :: GenParser Char st Char
76−decimalEntity = try $ do
65+decimalCharacterReference = try $ do
77 st <- string "&#"
66 st <- string "&#"
78 body <- many1 digit
67 body <- many1 digit
79 end <- char ';'
68 end <- char ';'
80 return $ chr $ read body
69 return $ chr $ read body
81
70
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−
105-- | Convert entities in a string to characters.
71-- | Convert entities in a string to characters.
106−decodeEntities :: String -> String
72+decodeCharacterReferences :: String -> String
107−decodeEntities str =
73+decodeCharacterReferences str =
108− case parse (many (characterEntity <|> anyChar)) str str of
74+ case parse (many (characterReference <|> anyChar)) str str of
109 Left err -> error $ "\nError: " ++ show err
75 Left err -> error $ "\nError: " ++ show err
110 Right result -> result
76 Right result -> result
111
77
112entityTable :: Map.Map String Char
78entityTable :: Map.Map String Char
113entityTable = Map.fromList entityTableList
79entityTable = Map.fromList entityTableList
114
80
115−reverseEntityTable :: Map.Map Char String
116−reverseEntityTable = Map.fromList $ map (\(a,b) -> (b,a)) entityTableList
117−
118entityTableList :: [(String, Char)]
81entityTableList :: [(String, Char)]
119entityTableList = [
82entityTableList = [
120 ("&quot;", chr 34),
83 ("&quot;", chr 34),
src/Text/Pandoc/Definition.hs+7−8
@@ -34,10 +34,10 @@ data Pandoc = Pandoc Meta [Block] deriving (Eq, Read, Show)
34
34
35-- | Bibliographic information for the document: title (list of 'Inline'),
35-- | Bibliographic information for the document: title (list of 'Inline'),
36-- authors (list of strings), date (string).
36-- authors (list of strings), date (string).
37−data Meta = Meta [Inline] -- title
37+data Meta = Meta [Inline] -- title
38− [String] -- authors
38+ [String] -- authors
39− String -- date
39+ String -- date
40− deriving (Eq, Show, Read)
40+ deriving (Eq, Show, Read)
41
41
42-- | Alignment of a table column.
42-- | Alignment of a table column.
43data Alignment = AlignLeft
43data Alignment = AlignLeft
@@ -65,12 +65,11 @@ data ListNumberDelim = DefaultDelim
65-- | Block element.
65-- | Block element.
66data Block
66data Block
67 = Plain [Inline] -- ^ Plain text, not a paragraph
67 = Plain [Inline] -- ^ Plain text, not a paragraph
68− | Null -- ^ Nothing
69 | Para [Inline] -- ^ Paragraph
68 | Para [Inline] -- ^ Paragraph
70 | CodeBlock String -- ^ Code block (literal)
69 | CodeBlock String -- ^ Code block (literal)
71 | RawHtml String -- ^ Raw HTML block (literal)
70 | RawHtml String -- ^ Raw HTML block (literal)
72 | BlockQuote [Block] -- ^ Block quote (list of blocks)
71 | BlockQuote [Block] -- ^ Block quote (list of blocks)
73− | OrderedList ListAttributes [[Block]] -- ^ Ordered list (attributes,
72+ | OrderedList ListAttributes [[Block]] -- ^ Ordered list (attributes
74 -- and a list of items, each a list of blocks)
73 -- and a list of items, each a list of blocks)
75 | BulletList [[Block]] -- ^ Bullet list (list of items, each
74 | BulletList [[Block]] -- ^ Bullet list (list of items, each
76 -- a list of blocks)
75 -- a list of blocks)
@@ -84,6 +83,7 @@ data Block
84 -- relative column widths, column headers
83 -- relative column widths, column headers
85 -- (each a list of blocks), and rows
84 -- (each a list of blocks), and rows
86 -- (each a list of lists of blocks)
85 -- (each a list of lists of blocks)
86+ | Null -- ^ Nothing
87 deriving (Eq, Read, Show)
87 deriving (Eq, Read, Show)
88
88
89-- | Type of quotation marks to use in Quoted inline.
89-- | Type of quotation marks to use in Quoted inline.
@@ -112,6 +112,5 @@ data Inline
112 | Link [Inline] Target -- ^ Hyperlink: text (list of inlines), target
112 | Link [Inline] Target -- ^ Hyperlink: text (list of inlines), target
113 | Image [Inline] Target -- ^ Image: alt text (list of inlines), target
113 | Image [Inline] Target -- ^ Image: alt text (list of inlines), target
114 -- and target
114 -- and target
115− | Note [Block] -- ^ Footnote or endnote - reference (string),
115+ | Note [Block] -- ^ Footnote or endnote
116− -- text (list of blocks)
117 deriving (Show, Eq, Read)
116 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 (
41 ) where
41 ) where
42
42
43import Text.ParserCombinators.Parsec
43import Text.ParserCombinators.Parsec
44−import Text.Pandoc.ParserCombinators
45import Text.Pandoc.Definition
44import Text.Pandoc.Definition
46import Text.Pandoc.Shared
45import Text.Pandoc.Shared
47−import Text.Pandoc.Entities ( characterEntity, decodeEntities )
46+import Text.Pandoc.CharacterReferences ( characterReference,
47+ decodeCharacterReferences )
48import Data.Maybe ( fromMaybe )
48import Data.Maybe ( fromMaybe )
49−import Data.List ( intersect, takeWhile, dropWhile )
49+import Data.List ( takeWhile, dropWhile )
50import Data.Char ( toUpper, toLower, isAlphaNum )
50import Data.Char ( toUpper, toLower, isAlphaNum )
51
51
52-- | Convert HTML-formatted string to 'Pandoc' document.
52-- | Convert HTML-formatted string to 'Pandoc' document.
@@ -55,10 +55,6 @@ readHtml :: ParserState -- ^ Parser state
55 -> Pandoc
55 -> Pandoc
56readHtml = readWith parseHtml
56readHtml = readWith parseHtml
57
57
58−-- for testing
59−testString :: String -> IO ()
60−testString = testStringWith parseHtml
61−
62--
58--
63-- Constants
59-- Constants
64--
60--
@@ -74,26 +70,18 @@ inlineHtmlTags = ["a", "abbr", "acronym", "b", "basefont", "bdo", "big",
74--
70--
75
71
76-- | Read blocks until end tag.
72-- | Read blocks until end tag.
77−blocksTilEnd tag = try (do
73+blocksTilEnd tag = do
78− blocks <- manyTill (do {b <- block; spaces; return b}) (htmlEndTag tag)
74+ blocks <- manyTill (block >>~ spaces) (htmlEndTag tag)
79− return $ filter (/= Null) blocks)
75+ return $ filter (/= Null) blocks
80
76
81-- | Read inlines until end tag.
77-- | Read inlines until end tag.
82−inlinesTilEnd tag = try (do
83− inlines <- manyTill inline (htmlEndTag tag)
78+inlinesTilEnd tag = manyTill inline (htmlEndTag tag)
84− return inlines)
85
79
86-- | Parse blocks between open and close tag.
80-- | 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
91
82
92-- | Parse inlines between open and close tag.
83-- | 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
97
85
98-- | Extract type from a tag: e.g. @br@ from @\<br\>@
86-- | Extract type from a tag: e.g. @br@ from @\<br\>@
99extractTagType :: String -> String
87extractTagType :: String -> String
@@ -103,19 +91,19 @@ extractTagType ('<':rest) =
103extractTagType _ = ""
91extractTagType _ = ""
104
92
105-- | Parse any HTML tag (closing or opening) and return text of tag
93-- | Parse any HTML tag (closing or opening) and return text of tag
106−anyHtmlTag = try (do
94+anyHtmlTag = try $ do
107 char '<'
95 char '<'
108 spaces
96 spaces
109 tag <- many1 alphaNum
97 tag <- many1 alphaNum
110 attribs <- htmlAttributes
98 attribs <- htmlAttributes
111 spaces
99 spaces
112 ender <- option "" (string "/")
100 ender <- option "" (string "/")
113− let ender' = if (null ender) then "" else " /"
101+ let ender' = if null ender then "" else " /"
114 spaces
102 spaces
115 char '>'
103 char '>'
116− return ("<" ++ tag ++ attribs ++ ender' ++ ">"))
104+ return $ "<" ++ tag ++ attribs ++ ender' ++ ">"
117
105
118−anyHtmlEndTag = try (do
106+anyHtmlEndTag = try $ do
119 char '<'
107 char '<'
120 spaces
108 spaces
121 char '/'
109 char '/'
@@ -123,19 +111,19 @@ anyHtmlEndTag = try (do
123 tagType <- many1 alphaNum
111 tagType <- many1 alphaNum
124 spaces
112 spaces
125 char '>'
113 char '>'
126− return ("</" ++ tagType ++ ">"))
114+ return $ "</" ++ tagType ++ ">"
127
115
128htmlTag :: String -> GenParser Char st (String, [(String, String)])
116htmlTag :: String -> GenParser Char st (String, [(String, String)])
129−htmlTag tag = try (do
117+htmlTag tag = try $ do
130 char '<'
118 char '<'
131 spaces
119 spaces
132 stringAnyCase tag
120 stringAnyCase tag
133 attribs <- many htmlAttribute
121 attribs <- many htmlAttribute
134 spaces
122 spaces
135− option "" (string "/")
123+ optional (string "/")
136 spaces
124 spaces
137 char '>'
125 char '>'
138− return (tag, (map (\(name, content, raw) -> (name, content)) attribs)))
126+ return (tag, (map (\(name, content, raw) -> (name, content)) attribs))
139
127
140-- parses a quoted html attribute value
128-- parses a quoted html attribute value
141quoted quoteChar = do
129quoted quoteChar = do
@@ -145,20 +133,20 @@ quoted quoteChar = do
145
133
146htmlAttributes = do
134htmlAttributes = do
147 attrList <- many htmlAttribute
135 attrList <- many htmlAttribute
148− return (concatMap (\(name, content, raw) -> raw) attrList)
136+ return $ concatMap (\(name, content, raw) -> raw) attrList
149
137
150htmlAttribute = htmlRegularAttribute <|> htmlMinimizedAttribute
138htmlAttribute = htmlRegularAttribute <|> htmlMinimizedAttribute
151
139
152−-- minimized boolean attribute (no = and value)
140+-- minimized boolean attribute
153−htmlMinimizedAttribute = try (do
141+htmlMinimizedAttribute = try $ do
154 many1 space
142 many1 space
155 name <- many1 (choice [letter, oneOf ".-_:"])
143 name <- many1 (choice [letter, oneOf ".-_:"])
156 spaces
144 spaces
157 notFollowedBy (char '=')
145 notFollowedBy (char '=')
158 let content = name
146 let content = name
159− return (name, content, (" " ++ name)))
147+ return (name, content, (" " ++ name))
160
148
161−htmlRegularAttribute = try (do
149+htmlRegularAttribute = try $ do
162 many1 space
150 many1 space
163 name <- many1 (choice [letter, oneOf ".-_:"])
151 name <- many1 (choice [letter, oneOf ".-_:"])
164 spaces
152 spaces
@@ -170,10 +158,10 @@ htmlRegularAttribute = try (do
170 a <- many (alphaNum <|> (oneOf "-._:"))
158 a <- many (alphaNum <|> (oneOf "-._:"))
171 return (a,"")) ]
159 return (a,"")) ]
172 return (name, content,
160 return (name, content,
173− (" " ++ name ++ "=" ++ quoteStr ++ content ++ quoteStr)))
161+ (" " ++ name ++ "=" ++ quoteStr ++ content ++ quoteStr))
174
162
175-- | Parse an end tag of type 'tag'
163-- | Parse an end tag of type 'tag'
176−htmlEndTag tag = try (do
164+htmlEndTag tag = try $ do
177 char '<'
165 char '<'
178 spaces
166 spaces
179 char '/'
167 char '/'
@@ -181,87 +169,83 @@ htmlEndTag tag = try (do
181 stringAnyCase tag
169 stringAnyCase tag
182 spaces
170 spaces
183 char '>'
171 char '>'
184− return ("</" ++ tag ++ ">"))
172+ return $ "</" ++ tag ++ ">"
185
173
186-- | Returns @True@ if the tag is an inline tag.
174-- | Returns @True@ if the tag is an inline tag.
187isInline tag = (extractTagType tag) `elem` inlineHtmlTags
175isInline tag = (extractTagType tag) `elem` inlineHtmlTags
188
176
189−anyHtmlBlockTag = try (do
177+anyHtmlBlockTag = try $ do
190− tag <- choice [anyHtmlTag, anyHtmlEndTag]
178+ tag <- anyHtmlTag <|> anyHtmlEndTag
191− if isInline tag then fail "inline tag" else return tag)
179+ if isInline tag then fail "inline tag" else return tag
192
180
193−anyHtmlInlineTag = try (do
181+anyHtmlInlineTag = try $ do
194− tag <- choice [ anyHtmlTag, anyHtmlEndTag ]
182+ tag <- anyHtmlTag <|> anyHtmlEndTag
195− if isInline tag then return tag else fail "not an inline tag")
183+ if isInline tag then return tag else fail "not an inline tag"
196
184
197-- | Parses material between script tags.
185-- | Parses material between script tags.
198-- Scripts must be treated differently, because they can contain '<>' etc.
186-- Scripts must be treated differently, because they can contain '<>' etc.
199−htmlScript = try (do
187+htmlScript = try $ do
200 open <- string "<script"
188 open <- string "<script"
201 rest <- manyTill anyChar (htmlEndTag "script")
189 rest <- manyTill anyChar (htmlEndTag "script")
202− return (open ++ rest ++ "</script>"))
190+ return $ open ++ rest ++ "</script>"
203
191
204htmlBlockElement = choice [ htmlScript, htmlComment, xmlDec, definition ]
192htmlBlockElement = choice [ htmlScript, htmlComment, xmlDec, definition ]
205
193
206−rawHtmlBlock = try (do
194+rawHtmlBlock = try $ do
207− notFollowedBy' (choice [htmlTag "/body", htmlTag "/html"])
195+ notFollowedBy' (htmlTag "/body" <|> htmlTag "/html")
208 body <- htmlBlockElement <|> anyHtmlBlockTag
196 body <- htmlBlockElement <|> anyHtmlBlockTag
209− sp <- (many space)
197+ sp <- many space
210 state <- getState
198 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
212
200
213-- | Parses an HTML comment.
201-- | Parses an HTML comment.
214−htmlComment = try (do
202+htmlComment = try $ do
215 string "<!--"
203 string "<!--"
216 comment <- manyTill anyChar (try (string "-->"))
204 comment <- manyTill anyChar (try (string "-->"))
217− return ("<!--" ++ comment ++ "-->"))
205+ return $ "<!--" ++ comment ++ "-->"
218
206
219--
207--
220-- parsing documents
208-- parsing documents
221--
209--
222
210
223−xmlDec = try (do
211+xmlDec = try $ do
224 string "<?"
212 string "<?"
225 rest <- manyTill anyChar (char '>')
213 rest <- manyTill anyChar (char '>')
226− return ("<?" ++ rest ++ ">"))
214+ return $ "<?" ++ rest ++ ">"
227
215
228−definition = try (do
216+definition = try $ do
229 string "<!"
217 string "<!"
230 rest <- manyTill anyChar (char '>')
218 rest <- manyTill anyChar (char '>')
231− return ("<!" ++ rest ++ ">"))
219+ return $ "<!" ++ rest ++ ">"
232
220
233−nonTitleNonHead = try (do
234− notFollowedBy' (htmlTag "title")
221+nonTitleNonHead = try $ notFollowedBy' (htmlTag "title" <|> htmlTag "/head") >>
235− notFollowedBy' (htmlTag "/head")
236− result <- choice [do {rawHtmlBlock; return ' '}, anyChar]
222+ ((rawHtmlBlock >> return ' ') <|> anyChar)
237− return result)
238
223
239−parseTitle = try (do
224+parseTitle = try $ do
240− (tag, attribs) <- htmlTag "title"
225+ (tag, _) <- htmlTag "title"
241 contents <- inlinesTilEnd tag
226 contents <- inlinesTilEnd tag
242 spaces
227 spaces
243− return contents)
228+ return contents
244
229
245-- parse header and return meta-information (for now, just title)
230-- parse header and return meta-information (for now, just title)
246−parseHead = try (do
231+parseHead = try $ do
247 htmlTag "head"
232 htmlTag "head"
248 spaces
233 spaces
249 skipMany nonTitleNonHead
234 skipMany nonTitleNonHead
250 contents <- option [] parseTitle
235 contents <- option [] parseTitle
251 skipMany nonTitleNonHead
236 skipMany nonTitleNonHead
252 htmlTag "/head"
237 htmlTag "/head"
253− return (contents, [], ""))
238+ return (contents, [], "")
254
239
255−skipHtmlTag tag = option ("",[]) (htmlTag tag)
240+skipHtmlTag tag = optional (htmlTag tag)
256
241
257-- h1 class="title" representation of title in body
242-- h1 class="title" representation of title in body
258−bodyTitle = try (do
243+bodyTitle = try $ do
259 (tag, attribs) <- htmlTag "h1"
244 (tag, attribs) <- htmlTag "h1"
260 cl <- case (extractAttribute "class" attribs) of
245 cl <- case (extractAttribute "class" attribs) of
261− Just "title" -> do {return ""}
246+ Just "title" -> return ""
262 otherwise -> fail "not title"
247 otherwise -> fail "not title"
263 inlinesTilEnd "h1"
248 inlinesTilEnd "h1"
264− return "")
265
249
266parseHtml = do
250parseHtml = do
267 sepEndBy (choice [xmlDec, definition, htmlComment]) spaces
251 sepEndBy (choice [xmlDec, definition, htmlComment]) spaces
@@ -271,27 +255,30 @@ parseHtml = do
271 spaces
255 spaces
272 skipHtmlTag "body"
256 skipHtmlTag "body"
273 spaces
257 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
275 blocks <- parseBlocks
259 blocks <- parseBlocks
276 spaces
260 spaces
277− option "" (htmlEndTag "body")
261+ optional (htmlEndTag "body")
278 spaces
262 spaces
279− option "" (htmlEndTag "html")
263+ optional (htmlEndTag "html")
280 many anyChar -- ignore anything after </html>
264 many anyChar -- ignore anything after </html>
281 eof
265 eof
282− return (Pandoc (Meta title authors date) blocks)
266+ return $ Pandoc (Meta title authors date) blocks
283
267
284--
268--
285-- parsing blocks
269-- parsing blocks
286--
270--
287
271
288−parseBlocks = do
289− spaces
290− result <- sepEndBy block spaces
272+parseBlocks = spaces >> sepEndBy block spaces >>= (return . filter (/= Null))
291− return $ filter (/= Null) result
292
273
293−block = choice [ codeBlock, header, hrule, list, blockQuote, para, plain,
274+block = choice [ codeBlock
275+ , header
276+ , hrule
277+ , list
278+ , blockQuote
279+ , para
280+ , plain
294− rawHtmlBlock ] <?> "block"
281+ , rawHtmlBlock ] <?> "block"
295
282
296--
283--
297-- header blocks
284-- header blocks
@@ -299,53 +286,49 @@ block = choice [ codeBlock, header, hrule, list, blockQuote, para, plain,
299
286
300header = choice (map headerLevel (enumFromTo 1 5)) <?> "header"
287header = choice (map headerLevel (enumFromTo 1 5)) <?> "header"
301
288
302−headerLevel n = try (do
289+headerLevel n = try $ do
303 let level = "h" ++ show n
290 let level = "h" ++ show n
304 (tag, attribs) <- htmlTag level
291 (tag, attribs) <- htmlTag level
305 contents <- inlinesTilEnd level
292 contents <- inlinesTilEnd level
306− return (Header n (normalizeSpaces contents)))
293+ return $ Header n (normalizeSpaces contents)
307
294
308--
295--
309-- hrule block
296-- hrule block
310--
297--
311
298
312−hrule = try (do
299+hrule = try $ do
313 (tag, attribs) <- htmlTag "hr"
300 (tag, attribs) <- htmlTag "hr"
314 state <- getState
301 state <- getState
315− if (not (null attribs)) && (stateParseRaw state)
302+ if not (null attribs) && stateParseRaw state
316− then -- in this case we want to parse it as raw html
303+ then unexpected "attributes in hr" -- parse as raw in this case
317− unexpected "attributes in hr"
318− else return HorizontalRule)
304+ else return HorizontalRule
319
305
320--
306--
321-- code blocks
307-- code blocks
322--
308--
323
309
324−codeBlock = choice [ preCodeBlock, bareCodeBlock ] <?> "code block"
310+codeBlock = preCodeBlock <|> bareCodeBlock <?> "code block"
325
311
326−preCodeBlock = try (do
312+preCodeBlock = try $ do
327 htmlTag "pre"
313 htmlTag "pre"
328 spaces
314 spaces
329− htmlTag "code"
315+ result <- bareCodeBlock
330− result <- manyTill anyChar (htmlEndTag "code")
331 spaces
316 spaces
332 htmlEndTag "pre"
317 htmlEndTag "pre"
333− return (CodeBlock (stripTrailingNewlines (decodeEntities result))))
318+ return result
334
319
335−bareCodeBlock = try (do
320+bareCodeBlock = try $ do
336 htmlTag "code"
321 htmlTag "code"
337 result <- manyTill anyChar (htmlEndTag "code")
322 result <- manyTill anyChar (htmlEndTag "code")
338− return (CodeBlock (stripTrailingNewlines (decodeEntities result))))
323+ return $ CodeBlock $ stripTrailingNewlines $
324+ decodeCharacterReferences result
339
325
340--
326--
341-- block quotes
327-- block quotes
342--
328--
343
329
344−blockQuote = try (do
345− tag <- htmlTag "blockquote"
330+blockQuote = try $ htmlTag "blockquote" >> spaces >>
346− spaces
347− blocks <- blocksTilEnd "blockquote"
331+ blocksTilEnd "blockquote" >>= (return . BlockQuote)
348− return (BlockQuote blocks))
349
332
350--
333--
351-- list blocks
334-- list blocks
@@ -354,119 +337,105 @@ blockQuote = try (do
354list = choice [ bulletList, orderedList, definitionList ] <?> "list"
337list = choice [ bulletList, orderedList, definitionList ] <?> "list"
355
338
356orderedList = try $ do
339orderedList = try $ do
357− (_, attribs) <- htmlTag "ol"
340+ (_, attribs) <- htmlTag "ol"
358− (start, style) <- option (1, DefaultStyle) $
341+ (start, style) <- option (1, DefaultStyle) $
359− do failIfStrict
342+ do failIfStrict
360− let sta = fromMaybe "1" $
343+ let sta = fromMaybe "1" $
361− lookup "start" attribs
344+ lookup "start" attribs
362− let sty = fromMaybe (fromMaybe "" $
345+ let sty = fromMaybe (fromMaybe "" $
363− lookup "style" attribs) $
346+ lookup "style" attribs) $
364− lookup "class" attribs
347+ lookup "class" attribs
365− let sty' = case sty of
348+ let sty' = case sty of
366− "lower-roman" -> LowerRoman
349+ "lower-roman" -> LowerRoman
367− "upper-roman" -> UpperRoman
350+ "upper-roman" -> UpperRoman
368− "lower-alpha" -> LowerAlpha
351+ "lower-alpha" -> LowerAlpha
369− "upper-alpha" -> UpperAlpha
352+ "upper-alpha" -> UpperAlpha
370− "decimal" -> Decimal
353+ "decimal" -> Decimal
371− _ -> DefaultStyle
354+ _ -> DefaultStyle
372− return (read sta, sty')
355+ return (read sta, sty')
373− spaces
356+ spaces
374− items <- sepEndBy1 (blocksIn "li") spaces
357+ items <- sepEndBy1 (blocksIn "li") spaces
375− htmlEndTag "ol"
358+ htmlEndTag "ol"
376− return (OrderedList (start, style, DefaultDelim) items)
359+ return $ OrderedList (start, style, DefaultDelim) items
377
360
378bulletList = try $ do
361bulletList = try $ do
379− htmlTag "ul"
362+ htmlTag "ul"
380− spaces
363+ spaces
381− items <- sepEndBy1 (blocksIn "li") spaces
364+ items <- sepEndBy1 (blocksIn "li") spaces
382− htmlEndTag "ul"
365+ htmlEndTag "ul"
383− return (BulletList items)
366+ return $ BulletList items
384
367
385definitionList = try $ do
368definitionList = try $ do
386− failIfStrict -- def lists not part of standard markdown
369+ failIfStrict -- def lists not part of standard markdown
387− tag <- htmlTag "dl"
370+ tag <- htmlTag "dl"
388− spaces
371+ spaces
389− items <- sepEndBy1 definitionListItem spaces
372+ items <- sepEndBy1 definitionListItem spaces
390− htmlEndTag "dl"
373+ htmlEndTag "dl"
391− return (DefinitionList items)
374+ return $ DefinitionList items
392
375
393definitionListItem = try $ do
376definitionListItem = try $ do
394− terms <- sepEndBy1 (inlinesIn "dt") spaces
377+ terms <- sepEndBy1 (inlinesIn "dt") spaces
395− defs <- sepEndBy1 (blocksIn "dd") spaces
378+ defs <- sepEndBy1 (blocksIn "dd") spaces
396− let term = joinWithSep [LineBreak] terms
379+ let term = joinWithSep [LineBreak] terms
397− return (term, concat defs)
380+ return (term, concat defs)
398
381
399--
382--
400-- paragraph block
383-- paragraph block
401--
384--
402
385
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
407
387
408--
388--
409-- plain block
389-- plain block
410--
390--
411
391
412−plain = do
413− result <- many1 inline
414− return (Plain (normalizeSpaces result))
392+plain = many1 inline >>= return . Plain . normalizeSpaces
415
393
416--
394--
417-- inline
395-- inline
418--
396--
419
397
420−inline = choice [ text, special ] <?> "inline"
398+inline = choice [ charRef
421−
399+ , strong
422−text = choice [ entity, strong, emph, superscript, subscript,
400+ , emph
401+ , superscript
402+ , subscript
403+ , strikeout
423− strikeout, spanStrikeout, code, str,
404+ , spanStrikeout
405+ , code
406+ , str
407+ , linebreak
424− linebreak, whitespace ] <?> "text"
408+ , whitespace
425−
409+ , link
410+ , image
426−special = choice [ link, image, rawHtmlInline ] <?>
411+ , rawHtmlInline ] <?> "inline"
427− "link, inline html, or image"
412+
428−
413+code = try $ do
429−entity = do
430− ent <- characterEntity
431− return $ Str [ent]
432−
433−code = try (do
434 htmlTag "code"
414 htmlTag "code"
435 result <- manyTill anyChar (htmlEndTag "code")
415 result <- manyTill anyChar (htmlEndTag "code")
436 -- remove internal line breaks, leading and trailing space,
416 -- remove internal line breaks, leading and trailing space,
437− -- and decode entities
417+ -- and decode character references
438− let result' = decodeEntities $ removeLeadingTrailingSpace $
418+ return $ Code $ decodeCharacterReferences $ removeLeadingTrailingSpace $
439− joinWithSep " " $ lines result
419+ joinWithSep " " $ lines result
440− return (Code result'))
441
420
442rawHtmlInline = do
421rawHtmlInline = do
443− result <- choice [htmlScript, anyHtmlInlineTag]
422+ result <- htmlScript <|> anyHtmlInlineTag
444 state <- getState
423 state <- getState
445 if stateParseRaw state then return (HtmlInline result) else return (Str "")
424 if stateParseRaw state then return (HtmlInline result) else return (Str "")
446
425
447−betweenTags tag = try (do
426+betweenTags tag = try $ htmlTag tag >> inlinesTilEnd tag >>=
448− htmlTag tag
449− result <- inlinesTilEnd tag
450− return (normalizeSpaces result))
427+ return . normalizeSpaces
451
428
452−emph = try (do
453− result <- choice [betweenTags "em", betweenTags "it"]
429+emph = (betweenTags "em" <|> betweenTags "it") >>= return . Emph
454− return (Emph result))
455
430
456−superscript = try $ do
431+strong = (betweenTags "b" <|> betweenTags "strong") >>= return . Strong
457− failIfStrict -- strict markdown has no superscript, so treat as raw HTML
458− result <- betweenTags "sup"
459− return (Superscript result)
460
432
461−subscript = try $ do
433+superscript = failIfStrict >> betweenTags "sup" >>= return . Superscript
462− failIfStrict -- strict markdown has no subscript, so treat as raw HTML
463− result <- betweenTags "sub"
464− return (Subscript result)
465
434
466−strikeout = try $ do
435+subscript = failIfStrict >> betweenTags "sub" >>= return . Subscript
467− failIfStrict -- strict markdown has no strikeout, so treat as raw HTML
436+
468− result <- choice [betweenTags "s", betweenTags "strike"]
437+strikeout = failIfStrict >> (betweenTags "s" <|> betweenTags "strike") >>=
469− return (Strikeout result)
438+ return . Strikeout
470
439
471spanStrikeout = try $ do
440spanStrikeout = try $ do
472 failIfStrict -- strict markdown has no strikeout, so treat as raw HTML
441 failIfStrict -- strict markdown has no strikeout, so treat as raw HTML
@@ -474,25 +443,14 @@ spanStrikeout = try $ do
474 result <- case (extractAttribute "class" attributes) of
443 result <- case (extractAttribute "class" attributes) of
475 Just "strikeout" -> inlinesTilEnd "span"
444 Just "strikeout" -> inlinesTilEnd "span"
476 _ -> fail "not a strikeout"
445 _ -> fail "not a strikeout"
477− return (Strikeout result)
446+ return $ Strikeout result
478
447
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
486
449
487-- hard line break
450-- hard line break
488−linebreak = do
489− htmlTag "br"
490− option ' ' newline
491− return LineBreak
451+linebreak = htmlTag "br" >> optional newline >> return LineBreak
492
452
493−str = do
494− result <- many1 (noneOf "<& \t\n")
453+str = many1 (noneOf "<& \t\n") >>= return . Str
495− return (Str result)
496
454
497--
455--
498-- links and images
456-- links and images
@@ -501,27 +459,27 @@ str = do
501-- extract contents of attribute (attribute names are case-insensitive)
459-- extract contents of attribute (attribute names are case-insensitive)
502extractAttribute name [] = Nothing
460extractAttribute name [] = Nothing
503extractAttribute name ((attrName, contents):rest) =
461extractAttribute name ((attrName, contents):rest) =
504− let name' = map toLower name
462+ let name' = map toLower name
505− attrName' = map toLower attrName in
463+ attrName' = map toLower attrName
506− if (attrName' == name')
464+ in if attrName' == name'
507− then Just (decodeEntities contents)
465+ then Just (decodeCharacterReferences contents)
508− else extractAttribute name rest
466+ else extractAttribute name rest
509
467
510link = try $ do
468link = try $ do
511 (tag, attributes) <- htmlTag "a"
469 (tag, attributes) <- htmlTag "a"
512 url <- case (extractAttribute "href" attributes) of
470 url <- case (extractAttribute "href" attributes) of
513− Just url -> do {return url}
471+ Just url -> return url
514 Nothing -> fail "no href"
472 Nothing -> fail "no href"
515− let title = fromMaybe "" (extractAttribute "title" attributes)
473+ let title = fromMaybe "" $ extractAttribute "title" attributes
516 label <- inlinesTilEnd "a"
474 label <- inlinesTilEnd "a"
517 return $ Link (normalizeSpaces label) (url, title)
475 return $ Link (normalizeSpaces label) (url, title)
518
476
519image = try $ do
477image = try $ do
520 (tag, attributes) <- htmlTag "img"
478 (tag, attributes) <- htmlTag "img"
521 url <- case (extractAttribute "src" attributes) of
479 url <- case (extractAttribute "src" attributes) of
522− Just url -> do {return url}
480+ Just url -> return url
523 Nothing -> fail "no src"
481 Nothing -> fail "no src"
524− let title = fromMaybe "" (extractAttribute "title" attributes)
482+ let title = fromMaybe "" $ extractAttribute "title" attributes
525 let alt = fromMaybe "" (extractAttribute "alt" attributes)
483 let alt = fromMaybe "" (extractAttribute "alt" attributes)
526 return $ Image [Str alt] (url, title)
484 return $ Image [Str alt] (url, title)
527
485
src/Text/Pandoc/Readers/LaTeX.hs+231−305
@@ -34,7 +34,6 @@ module Text.Pandoc.Readers.LaTeX (
34 ) where
34 ) where
35
35
36import Text.ParserCombinators.Parsec
36import Text.ParserCombinators.Parsec
37−import Text.Pandoc.ParserCombinators
38import Text.Pandoc.Definition
37import Text.Pandoc.Definition
39import Text.Pandoc.Shared
38import Text.Pandoc.Shared
40import Data.Maybe ( fromMaybe )
39import Data.Maybe ( fromMaybe )
@@ -47,9 +46,6 @@ readLaTeX :: ParserState -- ^ Parser state, including options for parser
47 -> Pandoc
46 -> Pandoc
48readLaTeX = readWith parseLaTeX
47readLaTeX = readWith parseLaTeX
49
48
50−-- for testing
51−testString = testStringWith parseLaTeX
52−
53-- characters with special meaning
49-- characters with special meaning
54specialChars = "\\$%&^&_~#{}\n \t|<>'\"-"
50specialChars = "\\$%&^&_~#{}\n \t|<>'\"-"
55
51
@@ -58,12 +54,12 @@ specialChars = "\\$%&^&_~#{}\n \t|<>'\"-"
58--
54--
59
55
60-- | Returns text between brackets and its matching pair.
56-- | Returns text between brackets and its matching pair.
61−bracketedText openB closeB = try (do
57+bracketedText openB closeB = do
62 result <- charsInBalanced' openB closeB
58 result <- charsInBalanced' openB closeB
63− return ([openB] ++ result ++ [closeB]))
59+ return $ [openB] ++ result ++ [closeB]
64
60
65-- | Returns an option or argument of a LaTeX command.
61-- | Returns an option or argument of a LaTeX command.
66−optOrArg = choice [ (bracketedText '{' '}'), (bracketedText '[' ']') ]
62+optOrArg = bracketedText '{' '}' <|> bracketedText '[' ']'
67
63
68-- | True if the string begins with '{'.
64-- | True if the string begins with '{'.
69isArg ('{':rest) = True
65isArg ('{':rest) = True
@@ -73,62 +69,55 @@ isArg other = False
73commandArgs = many optOrArg
69commandArgs = many optOrArg
74
70
75-- | Parses LaTeX command, returns (name, star, list of options or arguments).
71-- | Parses LaTeX command, returns (name, star, list of options or arguments).
76−command = try (do
72+command = try $ do
77 char '\\'
73 char '\\'
78 name <- many1 alphaNum
74 name <- many1 alphaNum
79 star <- option "" (string "*") -- some commands have starred versions
75 star <- option "" (string "*") -- some commands have starred versions
80 args <- commandArgs
76 args <- commandArgs
81− return (name, star, args))
77+ return (name, star, args)
82
78
83−begin name = try (do
79+begin name = try $ do
84− string "\\begin{"
80+ string $ "\\begin{" ++ name ++ "}"
85− string name
86− char '}'
87− option [] commandArgs
81+ optional commandArgs
88 spaces
82 spaces
89− return name)
83+ return name
90
84
91−end name = try (do
85+end name = try $ do
92− string "\\end{"
86+ string $ "\\end{" ++ name ++ "}"
93− string name
94− char '}'
95 spaces
87 spaces
96− return name)
88+ return name
97
89
98-- | Returns a list of block elements containing the contents of an
90-- | Returns a list of block elements containing the contents of an
99-- environment.
91-- environment.
100−environment name = try (do
92+environment name = try $ begin name >> spaces >> manyTill block (end name)
101− begin name
102− spaces
103− contents <- manyTill block (end name)
104− return contents)
105
93
106−anyEnvironment = try (do
94+anyEnvironment = try $ do
107 string "\\begin{"
95 string "\\begin{"
108 name <- many alphaNum
96 name <- many alphaNum
109 star <- option "" (string "*") -- some environments have starred variants
97 star <- option "" (string "*") -- some environments have starred variants
110 char '}'
98 char '}'
111− option [] commandArgs
99+ optional commandArgs
112 spaces
100 spaces
113 contents <- manyTill block (end (name ++ star))
101 contents <- manyTill block (end (name ++ star))
114− return (BlockQuote contents))
102+ return $ BlockQuote contents
115
103
116--
104--
117-- parsing documents
105-- parsing documents
118--
106--
119
107
120-- | Process LaTeX preamble, extracting metadata.
108-- | Process LaTeX preamble, extracting metadata.
121−processLaTeXPreamble = try (do
109+processLaTeXPreamble = try $ manyTill
122− manyTill (choice [bibliographic, comment, unknownCommand, nullBlock])
110+ (choice [bibliographic, comment, unknownCommand, nullBlock])
123− (try (string "\\begin{document}"))
111+ (try (string "\\begin{document}")) >>
124− spaces)
112+ spaces
125
113
126-- | Parse LaTeX and return 'Pandoc'.
114-- | Parse LaTeX and return 'Pandoc'.
127parseLaTeX = do
115parseLaTeX = do
128− option () processLaTeXPreamble -- preamble might not be present (fragment)
116+ optional processLaTeXPreamble -- preamble might not be present (fragment)
117+ spaces
129 blocks <- parseBlocks
118 blocks <- parseBlocks
130 spaces
119 spaces
131− option "" (try (string "\\end{document}")) -- might not be present (in fragment)
120+ optional $ try (string "\\end{document}") -- might not be present (fragment)
132 spaces
121 spaces
133 eof
122 eof
134 state <- getState
123 state <- getState
@@ -136,21 +125,27 @@ parseLaTeX = do
136 let title' = stateTitle state
125 let title' = stateTitle state
137 let authors' = stateAuthors state
126 let authors' = stateAuthors state
138 let date' = stateDate state
127 let date' = stateDate state
139− return (Pandoc (Meta title' authors' date') blocks')
128+ return $ Pandoc (Meta title' authors' date') blocks'
140
129
141--
130--
142-- parsing blocks
131-- parsing blocks
143--
132--
144
133
145−parseBlocks = do
134+parseBlocks = spaces >> many block
146− spaces
135+
147− result <- many block
148− return result
149−
150−block = choice [ hrule, codeBlock, header, list, blockQuote, mathBlock,
136+block = choice [ hrule
137+ , codeBlock
138+ , header
139+ , list
140+ , blockQuote
141+ , mathBlock
142+ , comment
143+ , bibliographic
144+ , para
151− comment, bibliographic, para, specialEnvironment,
145+ , specialEnvironment
146+ , itemBlock
152− itemBlock, unknownEnvironment, unknownCommand ] <?>
147+ , unknownEnvironment
153− "block"
148+ , unknownCommand ] <?> "block"
154
149
155--
150--
156-- header blocks
151-- header blocks
@@ -158,24 +153,21 @@ block = choice [ hrule, codeBlock, header, list, blockQuote, mathBlock,
158
153
159header = choice (map headerLevel (enumFromTo 1 5)) <?> "header"
154header = choice (map headerLevel (enumFromTo 1 5)) <?> "header"
160
155
161−headerLevel n = try (do
156+headerLevel n = try $ do
162 let subs = concat $ replicate (n - 1) "sub"
157 let subs = concat $ replicate (n - 1) "sub"
163 string ("\\" ++ subs ++ "section")
158 string ("\\" ++ subs ++ "section")
164− option ' ' (char '*')
159+ optional (char '*')
165 char '{'
160 char '{'
166 title <- manyTill inline (char '}')
161 title <- manyTill inline (char '}')
167 spaces
162 spaces
168− return (Header n (normalizeSpaces title)))
163+ return $ Header n (normalizeSpaces title)
169
164
170--
165--
171-- hrule block
166-- hrule block
172--
167--
173
168
174−hrule = try (do
175− oneOfStrings [ "\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n\n",
169+hrule = oneOfStrings [ "\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n\n",
176− "\\newpage" ]
177− spaces
178− return HorizontalRule)
170+ "\\newpage" ] >> spaces >> return HorizontalRule
179
171
180--
172--
181-- code blocks
173-- code blocks
@@ -183,37 +175,28 @@ hrule = try (do
183
175
184codeBlock = codeBlock1 <|> codeBlock2
176codeBlock = codeBlock1 <|> codeBlock2
185
177
186−codeBlock1 = try (do
178+codeBlock1 = try $ do
187 string "\\begin{verbatim}" -- don't use begin function because it
179 string "\\begin{verbatim}" -- don't use begin function because it
188 -- gobbles whitespace
180 -- gobbles whitespace
189− option "" blanklines -- we want to gobble blank lines, but not
181+ optional blanklines -- we want to gobble blank lines, but not
190 -- leading space
182 -- leading space
191 contents <- manyTill anyChar (try (string "\\end{verbatim}"))
183 contents <- manyTill anyChar (try (string "\\end{verbatim}"))
192 spaces
184 spaces
193− return (CodeBlock (stripTrailingNewlines contents)))
185+ return $ CodeBlock (stripTrailingNewlines contents)
194
186
195−codeBlock2 = try (do
187+codeBlock2 = try $ do
196− string "\\begin{Verbatim}" -- used by fancyverb package
188+ string "\\begin{Verbatim}" -- used by fancyvrb package
197 option "" blanklines
189 option "" blanklines
198 contents <- manyTill anyChar (try (string "\\end{Verbatim}"))
190 contents <- manyTill anyChar (try (string "\\end{Verbatim}"))
199 spaces
191 spaces
200− return (CodeBlock (stripTrailingNewlines contents)))
192+ return $ CodeBlock (stripTrailingNewlines contents)
201
193
202--
194--
203-- block quotes
195-- block quotes
204--
196--
205
197
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"
198+blockQuote = (environment "quote" <|> environment "quotation") >>~ spaces >>=
215− spaces
199+ return . BlockQuote
216− return (BlockQuote blocks))
217
200
218--
201--
219-- math block
202-- math block
@@ -223,12 +206,12 @@ mathBlock = mathBlockWith (begin "equation") (end "equation") <|>
223 mathBlockWith (begin "displaymath") (end "displaymath") <|>
206 mathBlockWith (begin "displaymath") (end "displaymath") <|>
224 mathBlockWith (string "\\[") (string "\\]") <?> "math block"
207 mathBlockWith (string "\\[") (string "\\]") <?> "math block"
225
208
226−mathBlockWith start end = try (do
209+mathBlockWith start end = try $ do
227 start
210 start
228 spaces
211 spaces
229 result <- manyTill anyChar end
212 result <- manyTill anyChar end
230 spaces
213 spaces
231− return (BlockQuote [Para [TeX ("$" ++ result ++ "$")]]))
214+ return $ BlockQuote [Para [TeX ("$" ++ result ++ "$")]]
232
215
233--
216--
234-- list blocks
217-- list blocks
@@ -237,69 +220,66 @@ mathBlockWith start end = try (do
237list = bulletList <|> orderedList <|> definitionList <?> "list"
220list = bulletList <|> orderedList <|> definitionList <?> "list"
238
221
239listItem = try $ do
222listItem = try $ do
240− ("item", _, args) <- command
223+ ("item", _, args) <- command
241− spaces
224+ spaces
242− state <- getState
225+ state <- getState
243− let oldParserContext = stateParserContext state
226+ let oldParserContext = stateParserContext state
244− updateState (\state -> state {stateParserContext = ListItemState})
227+ updateState (\state -> state {stateParserContext = ListItemState})
245− blocks <- many block
228+ blocks <- many block
246− updateState (\state -> state {stateParserContext = oldParserContext})
229+ updateState (\state -> state {stateParserContext = oldParserContext})
247− opt <- case args of
230+ opt <- case args of
248− ([x]) | "[" `isPrefixOf` x && "]" `isSuffixOf` x ->
231+ ([x]) | "[" `isPrefixOf` x && "]" `isSuffixOf` x ->
249− parseFromString (many inline) $ tail $ init x
232+ parseFromString (many inline) $ tail $ init x
250− _ -> return []
233+ _ -> return []
251− return (opt, blocks)
234+ return (opt, blocks)
252
235
253orderedList = try $ do
236orderedList = try $ do
254− string "\\begin{enumerate}"
237+ string "\\begin{enumerate}"
255− (_, style, delim) <- option (1, DefaultStyle, DefaultDelim) $
238+ (_, style, delim) <- option (1, DefaultStyle, DefaultDelim) $
256− try $ do failIfStrict
239+ try $ do failIfStrict
257− char '['
240+ char '['
258− res <- anyOrderedListMarker
241+ res <- anyOrderedListMarker
259− char ']'
242+ char ']'
260− return res
243+ return res
261− spaces
244+ spaces
262− option "" $ try $ do string "\\setlength{\\itemindent}"
245+ option "" $ try $ do string "\\setlength{\\itemindent}"
263− char '{'
246+ char '{'
264− manyTill anyChar (char '}')
247+ manyTill anyChar (char '}')
265− spaces
248+ spaces
266− start <- option 1 $ try $ do failIfStrict
249+ start <- option 1 $ try $ do failIfStrict
267− string "\\setcounter{enum"
250+ string "\\setcounter{enum"
268− many1 (char 'i')
251+ many1 (char 'i')
269− string "}{"
252+ string "}{"
270− num <- many1 digit
253+ num <- many1 digit
271− char '}'
254+ char '}'
272− spaces
255+ spaces
273− return $ (read num) + 1
256+ return $ (read num) + 1
274− items <- many listItem
257+ items <- many listItem
275− end "enumerate"
258+ end "enumerate"
276− spaces
259+ spaces
277− return $ OrderedList (start, style, delim) $ map snd items
260+ return $ OrderedList (start, style, delim) $ map snd items
278
261
279bulletList = try $ do
262bulletList = try $ do
280− begin "itemize"
263+ begin "itemize"
281− spaces
264+ spaces
282− items <- many listItem
265+ items <- many listItem
283− end "itemize"
266+ end "itemize"
284− spaces
267+ spaces
285− return (BulletList $ map snd items)
268+ return (BulletList $ map snd items)
286
269
287definitionList = try $ do
270definitionList = try $ do
288− begin "description"
271+ begin "description"
289− spaces
272+ spaces
290− items <- many listItem
273+ items <- many listItem
291− end "description"
274+ end "description"
292− spaces
275+ spaces
293− return (DefinitionList items)
276+ return (DefinitionList items)
294
277
295--
278--
296-- paragraph block
279-- paragraph block
297--
280--
298
281
299−para = try (do
300− result <- many1 inline
301− spaces
302− return (Para (normalizeSpaces result)))
282+para = many1 inline >>~ spaces >>= return . Para . normalizeSpaces
303
283
304--
284--
305-- title authors date
285-- title authors date
@@ -307,33 +287,30 @@ para = try (do
307
287
308bibliographic = choice [ maketitle, title, authors, date ]
288bibliographic = choice [ maketitle, title, authors, date ]
309
289
310−maketitle = try (do
311− string "\\maketitle"
290+maketitle = try (string "\\maketitle") >> spaces >> return Null
312− spaces
313− return Null)
314
291
315−title = try (do
292+title = try $ do
316 string "\\title{"
293 string "\\title{"
317 tit <- manyTill inline (char '}')
294 tit <- manyTill inline (char '}')
318 spaces
295 spaces
319 updateState (\state -> state { stateTitle = tit })
296 updateState (\state -> state { stateTitle = tit })
320− return Null)
297+ return Null
321
298
322−authors = try (do
299+authors = try $ do
323 string "\\author{"
300 string "\\author{"
324 authors <- manyTill anyChar (char '}')
301 authors <- manyTill anyChar (char '}')
325 spaces
302 spaces
326 let authors' = map removeLeadingTrailingSpace $ lines $
303 let authors' = map removeLeadingTrailingSpace $ lines $
327 substitute "\\\\" "\n" authors
304 substitute "\\\\" "\n" authors
328 updateState (\state -> state { stateAuthors = authors' })
305 updateState (\state -> state { stateAuthors = authors' })
329− return Null)
306+ return Null
330
307
331−date = try (do
308+date = try $ do
332 string "\\date{"
309 string "\\date{"
333 date' <- manyTill anyChar (char '}')
310 date' <- manyTill anyChar (char '}')
334 spaces
311 spaces
335 updateState (\state -> state { stateDate = date' })
312 updateState (\state -> state { stateDate = date' })
336− return Null)
313+ return Null
337
314
338--
315--
339-- item block
316-- item block
@@ -341,14 +318,14 @@ date = try (do
341--
318--
342
319
343-- this forces items to be parsed in different blocks
320-- this forces items to be parsed in different blocks
344−itemBlock = try (do
321+itemBlock = try $ do
345 ("item", _, args) <- command
322 ("item", _, args) <- command
346 state <- getState
323 state <- getState
347 if (stateParserContext state == ListItemState)
324 if (stateParserContext state == ListItemState)
348 then fail "item should be handled by list block"
325 then fail "item should be handled by list block"
349 else if null args
326 else if null args
350 then return Null
327 then return Null
351− else return (Plain [Str (stripFirstAndLast (head args))]))
328+ else return $ Plain [Str (stripFirstAndLast (head args))]
352
329
353--
330--
354-- raw LaTeX
331-- raw LaTeX
@@ -362,77 +339,93 @@ specialEnvironment = do -- these are always parsed as raw
362-- | Parse any LaTeX environment and return a Para block containing
339-- | Parse any LaTeX environment and return a Para block containing
363-- the whole literal environment as raw TeX.
340-- the whole literal environment as raw TeX.
364rawLaTeXEnvironment :: GenParser Char st Block
341rawLaTeXEnvironment :: GenParser Char st Block
365−rawLaTeXEnvironment = try (do
342+rawLaTeXEnvironment = try $ do
366− string "\\begin"
343+ string "\\begin{"
367− char '{'
368 name <- many1 alphaNum
344 name <- many1 alphaNum
369 star <- option "" (string "*") -- for starred variants
345 star <- option "" (string "*") -- for starred variants
370 let name' = name ++ star
346 let name' = name ++ star
371 char '}'
347 char '}'
372 args <- option [] commandArgs
348 args <- option [] commandArgs
373 let argStr = concat args
349 let argStr = concat args
374− contents <- manyTill (choice [(many1 (noneOf "\\")),
350+ contents <- manyTill (choice [ (many1 (noneOf "\\")),
375 (do
351 (do
376 (Para [TeX str]) <- rawLaTeXEnvironment
352 (Para [TeX str]) <- rawLaTeXEnvironment
377 return str),
353 return str),
378 string "\\" ])
354 string "\\" ])
379 (end name')
355 (end name')
380 spaces
356 spaces
381− return (Para [TeX ("\\begin{" ++ name' ++ "}" ++ argStr ++
357+ return $ Para [TeX $ "\\begin{" ++ name' ++ "}" ++ argStr ++
382− (concat contents) ++ "\\end{" ++ name' ++ "}")]))
358+ concat contents ++ "\\end{" ++ name' ++ "}"]
383
359
384−unknownEnvironment = try (do
360+unknownEnvironment = try $ do
385 state <- getState
361 state <- getState
386 result <- if stateParseRaw state -- check whether we should include raw TeX
362 result <- if stateParseRaw state -- check whether we should include raw TeX
387 then rawLaTeXEnvironment -- if so, get whole raw environment
363 then rawLaTeXEnvironment -- if so, get whole raw environment
388 else anyEnvironment -- otherwise just the contents
364 else anyEnvironment -- otherwise just the contents
389− return result)
365+ return result
390
366
391−unknownCommand = try (do
367+unknownCommand = try $ do
392− notFollowedBy' $ choice $ map end
368+ notFollowedBy' $ choice $ map end ["itemize", "enumerate", "description",
393− ["itemize", "enumerate", "description", "document"]
369+ "document"]
394 (name, star, args) <- command
370 (name, star, args) <- command
395 spaces
371 spaces
396 let argStr = concat args
372 let argStr = concat args
397 state <- getState
373 state <- getState
398− if (name == "item") && ((stateParserContext state) == ListItemState)
374+ if name == "item" && (stateParserContext state) == ListItemState
399 then fail "should not be parsed as raw"
375 then fail "should not be parsed as raw"
400 else string ""
376 else string ""
401 if stateParseRaw state
377 if stateParseRaw state
402− then return (Plain [TeX ("\\" ++ name ++ star ++ argStr)])
378+ then return $ Plain [TeX ("\\" ++ name ++ star ++ argStr)]
403− else return (Plain [Str (joinWithSep " " args)]))
379+ else return $ Plain [Str (joinWithSep " " args)]
404
380
405-- latex comment
381-- latex comment
406−comment = try (do
407− char '%'
408− result <- manyTill anyChar newline
382+comment = try $ char '%' >> manyTill anyChar newline >> spaces >> return Null
409− spaces
410− return Null)
411
383
412--
384--
413-- inline
385-- inline
414--
386--
415
387
416−inline = choice [ strong, emph, strikeout, superscript, subscript,
388+inline = choice [ strong
389+ , emph
390+ , strikeout
391+ , superscript
392+ , subscript
393+ , ref
394+ , lab
395+ , code
417− ref, lab, code, linebreak, spacer,
396+ , linebreak
418− math, ellipses, emDash, enDash, hyphen, quoted, apostrophe,
397+ , spacer
398+ , math
399+ , ellipses
400+ , emDash
401+ , enDash
402+ , hyphen
403+ , quoted
404+ , apostrophe
419− accentedChar, specialChar, specialInline, escapedChar,
405+ , accentedChar
406+ , specialChar
407+ , url
408+ , link
409+ , image
410+ , footnote
411+ , rawLaTeXInline
412+ , escapedChar
413+ , unescapedChar
414+ , str
415+ , endline
420− unescapedChar, str, endline, whitespace ] <?> "inline"
416+ , whitespace ] <?> "inline"
421−
422−specialInline = choice [ url, link, image, footnote, rawLaTeXInline ]
423− <?> "link, raw TeX, note, or image"
424
417
425accentedChar = normalAccentedChar <|> specialAccentedChar
418accentedChar = normalAccentedChar <|> specialAccentedChar
426
419
427−normalAccentedChar = try (do
420+normalAccentedChar = try $ do
428 char '\\'
421 char '\\'
429 accent <- oneOf "'`^\"~"
422 accent <- oneOf "'`^\"~"
430− character <- choice [ between (char '{') (char '}') anyChar, anyChar ]
423+ character <- (try $ char '{' >> alphaNum >>~ char '}') <|> alphaNum
431 let table = fromMaybe [] $ lookup character accentTable
424 let table = fromMaybe [] $ lookup character accentTable
432 let result = case lookup accent table of
425 let result = case lookup accent table of
433 Just num -> chr num
426 Just num -> chr num
434 Nothing -> '?'
427 Nothing -> '?'
435− return (Str [result]))
428+ return $ Str [result]
436
429
437-- an association list of letters and association list of accents
430-- an association list of letters and association list of accents
438-- and decimal character numbers.
431-- and decimal character numbers.
@@ -451,245 +444,179 @@ accentTable =
451 ('u', [('`', 249), ('\'', 250), ('^', 251), ('"', 252)]) ]
444 ('u', [('`', 249), ('\'', 250), ('^', 251), ('"', 252)]) ]
452
445
453specialAccentedChar = choice [ ccedil, aring, iuml, szlig, aelig,
446specialAccentedChar = choice [ ccedil, aring, iuml, szlig, aelig,
454− oslash, pound, euro, copyright, sect ]
447+ oslash, pound, euro, copyright, sect ]
455
448
456−ccedil = try (do
449+ccedil = try $ do
457 char '\\'
450 char '\\'
458 letter <- oneOfStrings ["cc", "cC"]
451 letter <- oneOfStrings ["cc", "cC"]
459 let num = if letter == "cc" then 231 else 199
452 let num = if letter == "cc" then 231 else 199
460− return (Str [chr num]))
453+ return $ Str [chr num]
461
454
462−aring = try (do
455+aring = try $ do
463 char '\\'
456 char '\\'
464 letter <- oneOfStrings ["aa", "AA"]
457 letter <- oneOfStrings ["aa", "AA"]
465 let num = if letter == "aa" then 229 else 197
458 let num = if letter == "aa" then 229 else 197
466− return (Str [chr num]))
459+ return $ Str [chr num]
467
460
468−iuml = try (do
469− string "\\\""
470− oneOfStrings ["\\i", "{\\i}"]
461+iuml = try (string "\\\"") >> oneOfStrings ["\\i", "{\\i}"] >>
471− return (Str [chr 239]))
462+ return (Str [chr 239])
472
463
473−icirc = try (do
474− string "\\^"
475− oneOfStrings ["\\i", "{\\i}"]
464+icirc = try (string "\\^") >> oneOfStrings ["\\i", "{\\i}"] >>
476− return (Str [chr 238]))
465+ return (Str [chr 238])
477
466
478−szlig = try (do
479− string "\\ss"
480− return (Str [chr 223]))
467+szlig = try (string "\\ss") >> return (Str [chr 223])
481
468
482−oslash = try (do
469+oslash = try $ do
483 char '\\'
470 char '\\'
484 letter <- choice [char 'o', char 'O']
471 letter <- choice [char 'o', char 'O']
485 let num = if letter == 'o' then 248 else 216
472 let num = if letter == 'o' then 248 else 216
486− return (Str [chr num]))
473+ return $ Str [chr num]
487
474
488−aelig = try (do
475+aelig = try $ do
489 char '\\'
476 char '\\'
490 letter <- oneOfStrings ["ae", "AE"]
477 letter <- oneOfStrings ["ae", "AE"]
491 let num = if letter == "ae" then 230 else 198
478 let num = if letter == "ae" then 230 else 198
492− return (Str [chr num]))
479+ return $ Str [chr num]
493
480
494−pound = try (do
495− string "\\pounds"
496− return (Str [chr 163]))
481+pound = try (string "\\pounds") >> return (Str [chr 163])
497
482
498−euro = try (do
499− string "\\euro"
500− return (Str [chr 8364]))
483+euro = try (string "\\euro") >> return (Str [chr 8364])
501
484
502−copyright = try (do
503− string "\\copyright"
485+copyright = try (string "\\copyright") >> return (Str [chr 169])
504− return (Str [chr 169]))
505
486
506−sect = try (do
507− string "\\S"
508− return (Str [chr 167]))
487+sect = try (string "\\S") >> return (Str [chr 167])
509
488
510escapedChar = do
489escapedChar = do
511 result <- escaped (oneOf " $%&_#{}\n")
490 result <- escaped (oneOf " $%&_#{}\n")
512− return (if result == Str "\n" then Str " " else result)
491+ return $ if result == Str "\n" then Str " " else result
513
492
514−unescapedChar = do -- ignore standalone, nonescaped special characters
493+-- ignore standalone, nonescaped special characters
515− oneOf "$^&_#{}|<>"
516− return (Str "")
494+unescapedChar = oneOf "$^&_#{}|<>" >> return (Str "")
517
495
518specialChar = choice [ backslash, tilde, caret, bar, lt, gt ]
496specialChar = choice [ backslash, tilde, caret, bar, lt, gt ]
519
497
520−backslash = try (do
521− string "\\textbackslash"
498+backslash = try (string "\\textbackslash") >> return (Str "\\")
522− return (Str "\\"))
523
499
524−tilde = try (do
525− string "\\ensuremath{\\sim}"
500+tilde = try (string "\\ensuremath{\\sim}") >> return (Str "~")
526− return (Str "~"))
527
501
528−caret = try (do
529− string "\\^{}"
530− return (Str "^"))
502+caret = try (string "\\^{}") >> return (Str "^")
531
503
532−bar = try (do
533− string "\\textbar"
504+bar = try (string "\\textbar") >> return (Str "\\")
534− return (Str "\\"))
535
505
536−lt = try (do
537− string "\\textless"
506+lt = try (string "\\textless") >> return (Str "<")
538− return (Str "<"))
539
507
540−gt = try (do
541− string "\\textgreater"
508+gt = try (string "\\textgreater") >> return (Str ">")
542− return (Str ">"))
543
509
544−code = try (do
510+code = try $ do
545 string "\\verb"
511 string "\\verb"
546 marker <- anyChar
512 marker <- anyChar
547 result <- manyTill anyChar (char marker)
513 result <- manyTill anyChar (char marker)
548− let result' = removeLeadingTrailingSpace result
549− return (Code result'))
514+ return $ Code $ removeLeadingTrailingSpace result
550
515
551−emph = try (do
552− oneOfStrings [ "\\emph{", "\\textit{" ]
516+emph = try $ oneOfStrings [ "\\emph{", "\\textit{" ] >>
553− result <- manyTill inline (char '}')
517+ manyTill inline (char '}') >>= return . Emph
554− return (Emph result))
555
518
556−strikeout = try $ do
557− string "\\sout{"
558− result <- manyTill inline (char '}')
519+strikeout = try $ string "\\sout{" >> manyTill inline (char '}') >>=
559− return (Strikeout result)
520+ return . Strikeout
560
521
561−superscript = try $ do
562− string "\\textsuperscript{"
522+superscript = try $ string "\\textsuperscript{" >>
563− result <- manyTill inline (char '}')
523+ manyTill inline (char '}') >>= return . Superscript
564− return (Superscript result)
565
524
566-- note: \textsubscript isn't a standard latex command, but we use
525-- note: \textsubscript isn't a standard latex command, but we use
567-- a defined version in pandoc.
526-- a defined version in pandoc.
568−subscript = try $ do
569− string "\\textsubscript{"
527+subscript = try $ string "\\textsubscript{" >> manyTill inline (char '}') >>=
570− result <- manyTill inline (char '}')
571− return (Subscript result)
528+ return . Subscript
572
529
573−apostrophe = do
574− char '\''
575− return Apostrophe
530+apostrophe = char '\'' >> return Apostrophe
576
531
577−quoted = do
578− doubleQuoted <|> singleQuoted
532+quoted = doubleQuoted <|> singleQuoted
579
533
580−singleQuoted = try (do
581− result <- enclosed singleQuoteStart singleQuoteEnd inline
534+singleQuoted = enclosed singleQuoteStart singleQuoteEnd inline >>=
582− return $ Quoted SingleQuote $ normalizeSpaces result)
535+ return . Quoted SingleQuote . normalizeSpaces
583
536
584−doubleQuoted = try (do
585− result <- enclosed doubleQuoteStart doubleQuoteEnd inline
537+doubleQuoted = enclosed doubleQuoteStart doubleQuoteEnd inline >>=
586− return $ Quoted DoubleQuote $ normalizeSpaces result)
538+ return . Quoted DoubleQuote . normalizeSpaces
587
539
588singleQuoteStart = char '`'
540singleQuoteStart = char '`'
589
541
590−singleQuoteEnd = char '\'' >> notFollowedBy alphaNum
542+singleQuoteEnd = try $ char '\'' >> notFollowedBy alphaNum
591
543
592doubleQuoteStart = string "``"
544doubleQuoteStart = string "``"
593
545
594doubleQuoteEnd = string "''"
546doubleQuoteEnd = string "''"
595
547
596−ellipses = try (do
597− string "\\ldots"
548+ellipses = try $ string "\\ldots" >> optional (try (string "{}")) >>
598− option "" (try (string "{}"))
599− return Ellipses)
549+ return Ellipses
600
550
601−enDash = try (do
602− string "--"
603− notFollowedBy (char '-')
604− return EnDash)
551+enDash = try (string "--") >> return EnDash
605
552
606−emDash = try (do
607− string "---"
608− return EmDash)
553+emDash = try (string "---") >> return EmDash
609
554
610−hyphen = do
611− char '-'
612− return (Str "-")
555+hyphen = char '-' >> return (Str "-")
613
556
614−lab = try (do
557+lab = try $ do
615 string "\\label{"
558 string "\\label{"
616 result <- manyTill anyChar (char '}')
559 result <- manyTill anyChar (char '}')
617− return (Str ("(" ++ result ++ ")")))
560+ return $ Str $ "(" ++ result ++ ")"
618
561
619−ref = try (do
620− string "\\ref{"
621− result <- manyTill anyChar (char '}')
562+ref = try (string "\\ref{") >> manyTill anyChar (char '}') >>= return . Str
622− return (Str (result)))
623
563
624−strong = try (do
625− string "\\textbf{"
626− result <- manyTill inline (char '}')
564+strong = try (string "\\textbf{") >> manyTill inline (char '}') >>=
627− return (Strong result))
565+ return . Strong
628
566
629−whitespace = do
630− many1 (oneOf "~ \t")
567+whitespace = many1 (oneOf "~ \t") >> return Space
631− return Space
632
568
633-- hard line break
569-- hard line break
634−linebreak = try (do
635− string "\\\\"
636− return LineBreak)
570+linebreak = try (string "\\\\") >> return LineBreak
637
571
638−spacer = try $ do
639− string "\\,"
640− return (Str "")
572+spacer = try (string "\\,") >> return (Str "")
641
573
642−str = do
643− result <- many1 (noneOf specialChars)
574+str = many1 (noneOf specialChars) >>= return . Str
644− return (Str result)
645
575
646-- endline internal to paragraph
576-- endline internal to paragraph
647−endline = try (do
648− newline
649− notFollowedBy blankline
577+endline = try $ newline >> notFollowedBy blankline >> return Space
650− return Space)
651
578
652-- math
579-- math
653math = math1 <|> math2 <?> "math"
580math = math1 <|> math2 <?> "math"
654
581
655−math1 = try (do
582+math1 = try $ do
656 char '$'
583 char '$'
657 result <- many (noneOf "$")
584 result <- many (noneOf "$")
658 char '$'
585 char '$'
659− return (TeX ("$" ++ result ++ "$")))
586+ return $ TeX ("$" ++ result ++ "$")
660
587
661−math2 = try (do
588+math2 = try $ do
662 string "\\("
589 string "\\("
663 result <- many (noneOf "$")
590 result <- many (noneOf "$")
664 string "\\)"
591 string "\\)"
665− return (TeX ("$" ++ result ++ "$")))
592+ return $ TeX ("$" ++ result ++ "$")
666
593
667--
594--
668-- links and images
595-- links and images
669--
596--
670
597
671−url = try (do
598+url = try $ do
672 string "\\url"
599 string "\\url"
673 url <- charsInBalanced '{' '}'
600 url <- charsInBalanced '{' '}'
674− return (Link [Code url] (url, "")))
601+ return $ Link [Code url] (url, "")
675
602
676−link = try (do
603+link = try $ do
677 string "\\href{"
604 string "\\href{"
678 url <- manyTill anyChar (char '}')
605 url <- manyTill anyChar (char '}')
679 char '{'
606 char '{'
680 label <- manyTill inline (char '}')
607 label <- manyTill inline (char '}')
681− return (Link (normalizeSpaces label) (url, "")))
608+ return $ Link (normalizeSpaces label) (url, "")
682
609
683−image = try (do
610+image = try $ do
684 ("includegraphics", _, args) <- command
611 ("includegraphics", _, args) <- command
685 let args' = filter isArg args -- filter out options
612 let args' = filter isArg args -- filter out options
686 let src = if null args' then
613 let src = if null args' then
687 ("", "")
614 ("", "")
688 else
615 else
689 (stripFirstAndLast (head args'), "")
616 (stripFirstAndLast (head args'), "")
690− return (Image [Str "image"] src))
617+ return $ Image [Str "image"] src
691
618
692−footnote = try (do
619+footnote = try $ do
693 (name, _, (contents:[])) <- command
620 (name, _, (contents:[])) <- command
694 if ((name == "footnote") || (name == "thanks"))
621 if ((name == "footnote") || (name == "thanks"))
695 then string ""
622 then string ""
@@ -700,16 +627,15 @@ footnote = try (do
700 setInput $ contents'
627 setInput $ contents'
701 blocks <- parseBlocks
628 blocks <- parseBlocks
702 setInput rest
629 setInput rest
703− return (Note blocks))
630+ return $ Note blocks
704
631
705-- | Parse any LaTeX command and return it in a raw TeX inline element.
632-- | Parse any LaTeX command and return it in a raw TeX inline element.
706rawLaTeXInline :: GenParser Char ParserState Inline
633rawLaTeXInline :: GenParser Char ParserState Inline
707−rawLaTeXInline = try (do
634+rawLaTeXInline = try $ do
708 (name, star, args) <- command
635 (name, star, args) <- command
709− let argStr = concat args
710 state <- getState
636 state <- getState
711 if ((name == "begin") || (name == "end") || (name == "item"))
637 if ((name == "begin") || (name == "end") || (name == "item"))
712 then fail "not an inline command"
638 then fail "not an inline command"
713 else string ""
639 else string ""
714− return (TeX ("\\" ++ name ++ star ++ argStr)))
640+ return $ TeX ("\\" ++ name ++ star ++ concat args)
715
641
src/Text/Pandoc/Readers/Markdown.hs+273−389
@@ -31,28 +31,24 @@ module Text.Pandoc.Readers.Markdown (
31 readMarkdown
31 readMarkdown
32 ) where
32 ) where
33
33
34−import Data.List ( findIndex, sortBy, transpose, isSuffixOf, intersect, lookup )
34+import Data.List ( transpose, isSuffixOf, lookup, sortBy )
35+import Data.Ord ( comparing )
35import Data.Char ( isAlphaNum )
36import Data.Char ( isAlphaNum )
36−import Text.Pandoc.ParserCombinators
37import Text.Pandoc.Definition
37import Text.Pandoc.Definition
38−import Text.Pandoc.Readers.LaTeX ( rawLaTeXInline, rawLaTeXEnvironment )
39import Text.Pandoc.Shared
38import Text.Pandoc.Shared
39+import Text.Pandoc.Readers.LaTeX ( rawLaTeXInline, rawLaTeXEnvironment )
40import Text.Pandoc.Readers.HTML ( rawHtmlBlock,
40import Text.Pandoc.Readers.HTML ( rawHtmlBlock,
41 anyHtmlBlockTag, anyHtmlInlineTag,
41 anyHtmlBlockTag, anyHtmlInlineTag,
42 anyHtmlTag, anyHtmlEndTag,
42 anyHtmlTag, anyHtmlEndTag,
43 htmlEndTag, extractTagType,
43 htmlEndTag, extractTagType,
44 htmlBlockElement )
44 htmlBlockElement )
45−import Text.Pandoc.Entities ( characterEntity, decodeEntities )
45+import Text.Pandoc.CharacterReferences ( decodeCharacterReferences )
46import Text.ParserCombinators.Parsec
46import Text.ParserCombinators.Parsec
47
47
48-- | Read markdown from an input string and return a Pandoc document.
48-- | Read markdown from an input string and return a Pandoc document.
49readMarkdown :: ParserState -> String -> Pandoc
49readMarkdown :: ParserState -> String -> Pandoc
50readMarkdown state str = (readWith parseMarkdown) state (str ++ "\n\n")
50readMarkdown state str = (readWith parseMarkdown) state (str ++ "\n\n")
51
51
52−-- | Parse markdown string with default options and print result (for testing).
53−testString :: String -> IO ()
54−testString = testStringWith parseMarkdown
55−
56--
52--
57-- Constants and data structure definitions
53-- Constants and data structure definitions
58--
54--
@@ -70,19 +66,16 @@ specialChars = "\\[]*_~`<>$!^-.&'\""
70-- auxiliary functions
66-- auxiliary functions
71--
67--
72
68
73−-- | Skip a single endline if there is one.
74−skipEndline = option Space endline
75−
76indentSpaces = try $ do
69indentSpaces = try $ do
77 state <- getState
70 state <- getState
78 let tabStop = stateTabStop state
71 let tabStop = stateTabStop state
79 try (count tabStop (char ' ')) <|>
72 try (count tabStop (char ' ')) <|>
80− (do{many (char ' '); string "\t"}) <?> "indentation"
73+ (many (char ' ') >> string "\t") <?> "indentation"
81
74
82nonindentSpaces = do
75nonindentSpaces = do
83 state <- getState
76 state <- getState
84 let tabStop = stateTabStop state
77 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)]
86
79
87-- | Fail unless we're at beginning of a line.
80-- | Fail unless we're at beginning of a line.
88failUnlessBeginningOfLine = do
81failUnlessBeginningOfLine = do
@@ -94,20 +87,21 @@ failUnlessSmart = do
94 state <- getState
87 state <- getState
95 if stateSmart state then return () else fail "Smart typography feature"
88 if stateSmart state then return () else fail "Smart typography feature"
96
89
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+
97-- | Parse a sequence of inline elements between a string
95-- | Parse a sequence of inline elements between a string
98-- @opener@ and a string @closer@, including inlines
96-- @opener@ and a string @closer@, including inlines
99-- between balanced pairs of @opener@ and a @closer@.
97-- between balanced pairs of @opener@ and a @closer@.
100inlinesInBalanced :: String -> String -> GenParser Char ParserState [Inline]
98inlinesInBalanced :: String -> String -> GenParser Char ParserState [Inline]
101inlinesInBalanced opener closer = try $ do
99inlinesInBalanced 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)
100+ string opener
108− result <- manyTill ( (do lookAhead openerSymbol
101+ result <- manyTill ( (do lookAhead (inlineString opener)
102+ -- because it might be a link...
109− bal <- inlinesInBalanced opener closer
103+ bal <- inlinesInBalanced opener closer
110− return $ [Str opener] ++ bal ++ [Str closer])
104+ return $ [Str opener] ++ bal ++ [Str closer])
111 <|> (count 1 inline))
105 <|> (count 1 inline))
112 (try (string closer))
106 (try (string closer))
113 return $ concat result
107 return $ concat result
@@ -116,59 +110,55 @@ inlinesInBalanced opener closer = try $ do
116-- document structure
110-- document structure
117--
111--
118
112
119−titleLine = try (do
120− char '%'
121− skipSpaces
122− line <- manyTill inline newline
113+titleLine = try $ char '%' >> skipSpaces >> manyTill inline newline
123− return line)
124
114
125−authorsLine = try (do
115+authorsLine = try $ do
126 char '%'
116 char '%'
127 skipSpaces
117 skipSpaces
128 authors <- sepEndBy (many1 (noneOf ",;\n")) (oneOf ",;")
118 authors <- sepEndBy (many1 (noneOf ",;\n")) (oneOf ",;")
129 newline
119 newline
130− return (map (decodeEntities . removeLeadingTrailingSpace) authors))
120+ return $ map (decodeCharacterReferences . removeLeadingTrailingSpace) authors
131
121
132−dateLine = try (do
122+dateLine = try $ do
133 char '%'
123 char '%'
134 skipSpaces
124 skipSpaces
135 date <- many (noneOf "\n")
125 date <- many (noneOf "\n")
136 newline
126 newline
137− return (decodeEntities $ removeTrailingSpace date))
127+ return $ decodeCharacterReferences $ removeTrailingSpace date
138
128
139−titleBlock = try (do
129+titleBlock = try $ do
140 failIfStrict
130 failIfStrict
141 title <- option [] titleLine
131 title <- option [] titleLine
142 author <- option [] authorsLine
132 author <- option [] authorsLine
143 date <- option "" dateLine
133 date <- option "" dateLine
144− option "" blanklines
134+ optional blanklines
145− return (title, author, date))
135+ return (title, author, date)
146
136
147parseMarkdown = do
137parseMarkdown = do
138+ -- markdown allows raw HTML
148− updateState (\state -> state { stateParseRaw = True }) -- markdown allows raw HTML
139+ updateState (\state -> state { stateParseRaw = True })
149 (title, author, date) <- option ([],[],"") titleBlock
140 (title, author, date) <- option ([],[],"") titleBlock
150 -- go through once just to get list of reference keys
141 -- go through once just to get list of reference keys
151− refs <- manyTill (referenceKey <|> (do l <- lineClump
142+ refs <- manyTill (referenceKey <|> (lineClump >>= return . LineClump)) eof
152− return (LineClump l))) eof
153 let keys = map (\(KeyBlock label target) -> (label, target)) $
143 let keys = map (\(KeyBlock label target) -> (label, target)) $
154 filter isKeyBlock refs
144 filter isKeyBlock refs
155 let rawlines = map (\(LineClump ln) -> ln) $ filter isLineClump refs
145 let rawlines = map (\(LineClump ln) -> ln) $ filter isLineClump refs
156 setInput $ concat rawlines -- with keys stripped out
146 setInput $ concat rawlines -- with keys stripped out
157 updateState (\state -> state { stateKeys = keys })
147 updateState (\state -> state { stateKeys = keys })
158− -- now go through for notes
148+ -- now go through for notes (which may contain references - hence 2nd pass)
159− refs <- manyTill (noteBlock <|> (do l <- lineClump
149+ refs <- manyTill (noteBlock <|> (lineClump >>= return . LineClump)) eof
160− return (LineClump l))) eof
161 let notes = map (\(NoteBlock label blocks) -> (label, blocks)) $
150 let notes = map (\(NoteBlock label blocks) -> (label, blocks)) $
162 filter isNoteBlock refs
151 filter isNoteBlock refs
163 let rawlines = map (\(LineClump ln) -> ln) $ filter isLineClump refs
152 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
165 updateState (\state -> state { stateNotes = notes })
155 updateState (\state -> state { stateNotes = notes })
166− blocks <- parseBlocks -- go through again, for real
156+ blocks <- parseBlocks
167 let blocks' = filter (/= Null) blocks
157 let blocks' = filter (/= Null) blocks
168− return (Pandoc (Meta title author date) blocks')
158+ return $ Pandoc (Meta title author date) blocks'
169
159
170--
160--
171−-- initial pass for references
161+-- initial pass for references and notes
172--
162--
173
163
174referenceKey = try $ do
164referenceKey = try $ do
@@ -176,9 +166,9 @@ referenceKey = try $ do
176 label <- reference
166 label <- reference
177 char ':'
167 char ':'
178 skipSpaces
168 skipSpaces
179− option ' ' (char '<')
169+ optional (char '<')
180 src <- many (noneOf "> \n\t")
170 src <- many (noneOf "> \n\t")
181− option ' ' (char '>')
171+ optional (char '>')
182 tit <- option "" title
172 tit <- option "" title
183 blanklines
173 blanklines
184 return $ KeyBlock label (removeTrailingSpace src, tit)
174 return $ KeyBlock label (removeTrailingSpace src, tit)
@@ -189,33 +179,28 @@ noteMarker = try $ do
189 manyTill (noneOf " \t\n") (char ']')
179 manyTill (noneOf " \t\n") (char ']')
190
180
191rawLine = try $ do
181rawLine = try $ do
192− notFollowedBy' blankline
182+ notFollowedBy blankline
193 notFollowedBy' noteMarker
183 notFollowedBy' noteMarker
194 contents <- many1 nonEndline
184 contents <- many1 nonEndline
195− end <- option "" (do
185+ end <- option "" (newline >> optional indentSpaces >> return "\n")
196− newline
197− option "" indentSpaces
198− return "\n")
199− return (contents ++ end)
186+ return $ contents ++ end
200
187
201−rawLines = do
202− lines <- many1 rawLine
188+rawLines = many1 rawLine >>= return . concat
203− return (concat lines)
204
189
205noteBlock = try $ do
190noteBlock = try $ do
206 failIfStrict
191 failIfStrict
207 ref <- noteMarker
192 ref <- noteMarker
208 char ':'
193 char ':'
209− option ' ' blankline
194+ optional blankline
210− option "" indentSpaces
195+ optional indentSpaces
211− raw <- sepBy rawLines (try (do {blankline; indentSpaces}))
196+ raw <- sepBy rawLines (try (blankline >> indentSpaces))
212− option "" blanklines
197+ optional blanklines
213 -- parse the extracted text, which may contain various block elements:
198 -- parse the extracted text, which may contain various block elements:
214 rest <- getInput
199 rest <- getInput
215 setInput $ (joinWithSep "\n" raw) ++ "\n\n"
200 setInput $ (joinWithSep "\n" raw) ++ "\n\n"
216 contents <- parseBlocks
201 contents <- parseBlocks
217 setInput rest
202 setInput rest
218− return (NoteBlock ref contents)
203+ return $ NoteBlock ref contents
219
204
220--
205--
221-- parsing blocks
206-- parsing blocks
@@ -239,48 +224,39 @@ block = choice [ header
239-- header blocks
224-- header blocks
240--
225--
241
226
242−header = choice [ setextHeader, atxHeader ] <?> "header"
227+header = setextHeader <|> atxHeader <?> "header"
243
228
244−atxHeader = try (do
229+atxHeader = try $ do
245 lead <- many1 (char '#')
230 lead <- many1 (char '#')
246− notFollowedBy (char '.') -- this would be a list
231+ notFollowedBy (char '.' <|> char ')') -- this would be a list
247− notFollowedBy (char ')')
248 skipSpaces
232 skipSpaces
249 txt <- manyTill inline atxClosing
233 txt <- manyTill inline atxClosing
250− return (Header (length lead) (normalizeSpaces txt)))
234+ return $ Header (length lead) (normalizeSpaces txt)
251
235
252−atxClosing = try (do
236+atxClosing = try $ skipMany (char '#') >> skipSpaces >> newline >>
253− skipMany (char '#')
254− skipSpaces
255− newline
256− option "" blanklines)
237+ option "" blanklines
257
238
258setextHeader = choice $
239setextHeader = choice $
259− map (\x -> setextH x) (enumFromTo 1 (length setextHChars))
240+ map (\x -> setextH x) $ enumFromTo 1 (length setextHChars)
260
241
261−setextH n = try (do
242+setextH n = try $ do
262 txt <- many1Till inline newline
243 txt <- many1Till inline newline
263 many1 (char (setextHChars !! (n-1)))
244 many1 (char (setextHChars !! (n-1)))
264 skipSpaces
245 skipSpaces
265 newline
246 newline
266− option "" blanklines
247+ optional blanklines
267− return (Header n (normalizeSpaces txt)))
248+ return $ Header n (normalizeSpaces txt)
268
249
269--
250--
270-- hrule block
251-- hrule block
271--
252--
272
253
273−hruleWith chr = try (do
254+hruleWith chr = try $ do
274− skipSpaces
275− char chr
276− skipSpaces
277− char chr
278− skipSpaces
255+ count 3 (skipSpaces >> char chr)
279− char chr
280− skipMany (oneOf (chr:spaceChars))
256+ skipMany (skipSpaces >> char chr)
281 newline
257 newline
282− option "" blanklines
258+ optional blanklines
283− return HorizontalRule)
259+ return HorizontalRule
284
260
285hrule = choice (map hruleWith hruleChars) <?> "hrule"
261hrule = choice (map hruleWith hruleChars) <?> "hrule"
286
262
@@ -288,67 +264,55 @@ hrule = choice (map hruleWith hruleChars) <?> "hrule"
288-- code blocks
264-- code blocks
289--
265--
290
266
291−indentedLine = try (do
267+indentedLine = try $ do
292 indentSpaces
268 indentSpaces
293 result <- manyTill anyChar newline
269 result <- manyTill anyChar newline
294− return (result ++ "\n"))
270+ return $ result ++ "\n"
295
271
296-- two or more indented lines, possibly separated by blank lines
272-- two or more indented lines, possibly separated by blank lines
297−indentedBlock = try (do
273+indentedBlock = try $ do
298 res1 <- indentedLine
274 res1 <- indentedLine
299 blanks <- many blankline
275 blanks <- many blankline
300− res2 <- choice [indentedBlock, indentedLine]
276+ res2 <- indentedBlock <|> indentedLine
301− return (res1 ++ blanks ++ res2))
277+ return $ res1 ++ blanks ++ res2
302
278
303−codeBlock = do
304− result <- choice [indentedBlock, indentedLine]
279+codeBlock = (indentedBlock <|> indentedLine) >>~ optional blanklines >>=
305− option "" blanklines
306− return (CodeBlock (stripTrailingNewlines result))
280+ return . CodeBlock . stripTrailingNewlines
307
281
308--
282--
309-- block quotes
283-- block quotes
310--
284--
311
285
312−emacsBoxQuote = try (do
286+emacsBoxQuote = try $ do
313 failIfStrict
287 failIfStrict
314 string ",----"
288 string ",----"
315 manyTill anyChar newline
289 manyTill anyChar newline
316− raw <- manyTill (try (do
290+ raw <- manyTill
317− char '|'
318− option ' ' (char ' ')
319− result <- manyTill anyChar newline
291+ (try (char '|' >> optional (char ' ') >> manyTill anyChar newline))
320− return result))
321− (string "`----")
292+ (try (string "`----"))
322− manyTill anyChar newline
323− option "" blanklines
293+ blanklines
324− return raw)
294+ return raw
325
295
326−emailBlockQuoteStart = try (do
296+emailBlockQuoteStart = try $ nonindentSpaces >> char '>' >>~ optional (char ' ')
327− nonindentSpaces
328− char '>'
329− option ' ' (char ' ')
330− return "> ")
331
297
332−emailBlockQuote = try (do
298+emailBlockQuote = try $ do
333 emailBlockQuoteStart
299 emailBlockQuoteStart
334− raw <- sepBy (many (choice [nonEndline,
300+ raw <- sepBy (many (nonEndline <|>
335− (try (do
336− endline
337− notFollowedBy' emailBlockQuoteStart
301+ (try (endline >> notFollowedBy emailBlockQuoteStart >>
338− return '\n'))]))
302+ return '\n'))))
339− (try (do {newline; emailBlockQuoteStart}))
303+ (try (newline >> emailBlockQuoteStart))
340− newline <|> (do{ eof; return '\n' })
304+ newline <|> (eof >> return '\n')
341− option "" blanklines
305+ optional blanklines
342− return raw)
306+ return raw
343
307
344blockQuote = do
308blockQuote = do
345− raw <- choice [ emailBlockQuote, emacsBoxQuote ]
309+ raw <- emailBlockQuote <|> emacsBoxQuote
346 -- parse the extracted block, which may contain various block elements:
310 -- parse the extracted block, which may contain various block elements:
347 rest <- getInput
311 rest <- getInput
348 setInput $ (joinWithSep "\n" raw) ++ "\n\n"
312 setInput $ (joinWithSep "\n" raw) ++ "\n\n"
349 contents <- parseBlocks
313 contents <- parseBlocks
350 setInput rest
314 setInput rest
351− return (BlockQuote contents)
315+ return $ BlockQuote contents
352
316
353--
317--
354-- list blocks
318-- list blocks
@@ -357,7 +321,7 @@ blockQuote = do
357list = choice [ bulletList, orderedList, definitionList ] <?> "list"
321list = choice [ bulletList, orderedList, definitionList ] <?> "list"
358
322
359bulletListStart = try $ do
323bulletListStart = 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
361 nonindentSpaces
325 nonindentSpaces
362 notFollowedBy' hrule -- because hrules start out just like lists
326 notFollowedBy' hrule -- because hrules start out just like lists
363 oneOf bulletListMarkers
327 oneOf bulletListMarkers
@@ -365,7 +329,7 @@ bulletListStart = try $ do
365 skipSpaces
329 skipSpaces
366
330
367anyOrderedListStart = try $ do
331anyOrderedListStart = 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
369 nonindentSpaces
333 nonindentSpaces
370 state <- getState
334 state <- getState
371 if stateStrict state
335 if stateStrict state
@@ -375,7 +339,7 @@ anyOrderedListStart = try $ do
375 else anyOrderedListMarker
339 else anyOrderedListMarker
376
340
377orderedListStart style delim = try $ do
341orderedListStart 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
379 nonindentSpaces
343 nonindentSpaces
380 state <- getState
344 state <- getState
381 if stateStrict state
345 if stateStrict state
@@ -387,40 +351,39 @@ orderedListStart style delim = try $ do
387 skipSpaces
351 skipSpaces
388
352
389-- parse a line of a list item (start = parser for beginning of list item)
353-- parse a line of a list item (start = parser for beginning of list item)
390−listLine start = try (do
354+listLine start = try $ do
391 notFollowedBy' start
355 notFollowedBy' start
392 notFollowedBy blankline
356 notFollowedBy blankline
393− notFollowedBy' (do
357+ notFollowedBy' (do indentSpaces
394− indentSpaces
395− many (spaceChar)
358+ many (spaceChar)
396− choice [bulletListStart, anyOrderedListStart >> return ()])
359+ bulletListStart <|> (anyOrderedListStart >> return ()))
397 line <- manyTill anyChar newline
360 line <- manyTill anyChar newline
398− return (line ++ "\n"))
361+ return $ line ++ "\n"
399
362
400-- parse raw text for one list item, excluding start marker and continuations
363-- parse raw text for one list item, excluding start marker and continuations
401−rawListItem start = try (do
364+rawListItem start = try $ do
402 start
365 start
403 result <- many1 (listLine start)
366 result <- many1 (listLine start)
404 blanks <- many blankline
367 blanks <- many blankline
405− return ((concat result) ++ blanks))
368+ return $ concat result ++ blanks
406
369
407-- continuation of a list item - indented and separated by blankline
370-- continuation of a list item - indented and separated by blankline
408-- or (in compact lists) endline.
371-- or (in compact lists) endline.
409-- note: nested lists are parsed as continuations
372-- note: nested lists are parsed as continuations
410−listContinuation start = try (do
373+listContinuation start = try $ do
411 lookAhead indentSpaces
374 lookAhead indentSpaces
412 result <- many1 (listContinuationLine start)
375 result <- many1 (listContinuationLine start)
413 blanks <- many blankline
376 blanks <- many blankline
414− return ((concat result) ++ blanks))
377+ return $ concat result ++ blanks
415
378
416−listContinuationLine start = try (do
379+listContinuationLine start = try $ do
417− notFollowedBy' blankline
380+ notFollowedBy blankline
418 notFollowedBy' start
381 notFollowedBy' start
419− option "" indentSpaces
382+ optional indentSpaces
420 result <- manyTill anyChar newline
383 result <- manyTill anyChar newline
421− return (result ++ "\n"))
384+ return $ result ++ "\n"
422
385
423−listItem start = try (do
386+listItem start = try $ do
424 first <- rawListItem start
387 first <- rawListItem start
425 continuations <- many (listContinuation start)
388 continuations <- many (listContinuation start)
426 -- parsing with ListItemState forces markers at beginning of lines to
389 -- parsing with ListItemState forces markers at beginning of lines to
@@ -436,18 +399,15 @@ listItem start = try (do
436 contents <- parseBlocks
399 contents <- parseBlocks
437 setInput rest
400 setInput rest
438 updateState (\st -> st {stateParserContext = oldContext})
401 updateState (\st -> st {stateParserContext = oldContext})
439− return contents)
402+ return contents
440
403
441−orderedList = try (do
404+orderedList = do
442 (start, style, delim) <- lookAhead anyOrderedListStart
405 (start, style, delim) <- lookAhead anyOrderedListStart
443 items <- many1 (listItem (orderedListStart style delim))
406 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
446
408
447−bulletList = try (do
448− items <- many1 (listItem bulletListStart)
409+bulletList = many1 (listItem bulletListStart) >>=
449− let items' = compactify items
450− return (BulletList items'))
410+ return . BulletList . compactify
451
411
452-- definition lists
412-- definition lists
453
413
@@ -470,9 +430,9 @@ defRawBlock = try $ do
470 char ':'
430 char ':'
471 state <- getState
431 state <- getState
472 let tabStop = stateTabStop state
432 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")
474 firstline <- anyLine
434 firstline <- anyLine
475− rawlines <- many (do {notFollowedBy' blankline; indentSpaces; anyLine})
435+ rawlines <- many (notFollowedBy blankline >> indentSpaces >> anyLine)
476 trailing <- option "" blanklines
436 trailing <- option "" blanklines
477 return $ firstline ++ "\n" ++ unlines rawlines ++ trailing
437 return $ firstline ++ "\n" ++ unlines rawlines ++ trailing
478
438
@@ -488,71 +448,62 @@ definitionList = do
488-- paragraph block
448-- paragraph block
489--
449--
490
450
491−para = try (do
451+para = try $ do
492 result <- many1 inline
452 result <- many1 inline
493 newline
453 newline
494 st <- getState
454 st <- getState
495 if stateStrict st
455 if stateStrict st
496− then choice [lookAhead blockQuote, lookAhead header,
456+ then choice [ lookAhead blockQuote, lookAhead header,
497− (do{blanklines; return Null})]
457+ (blanklines >> return Null) ]
498− else choice [(do{lookAhead emacsBoxQuote; return Null}),
458+ else choice [ lookAhead emacsBoxQuote >> return Null,
499− (do{blanklines; return Null})]
459+ (blanklines >> return Null) ]
500− let result' = normalizeSpaces result
501− return (Para result'))
460+ return $ Para $ normalizeSpaces result
502−
461+
503−plain = do
462+plain = many1 inline >>= return . Plain . normalizeSpaces
504− result <- many1 inline
505− let result' = normalizeSpaces result
506− return (Plain result')
507
463
508--
464--
509-- raw html
465-- raw html
510--
466--
511
467
512−htmlElement = choice [strictHtmlBlock,
513− htmlBlockElement] <?> "html element"
468+htmlElement = strictHtmlBlock <|> htmlBlockElement <?> "html element"
514
469
515htmlBlock = do
470htmlBlock = do
516 st <- getState
471 st <- getState
517 if stateStrict st
472 if stateStrict st
518− then do
519− failUnlessBeginningOfLine
473+ then try $ do failUnlessBeginningOfLine
520− first <- htmlElement
474+ first <- htmlElement
521− finalSpace <- many (oneOf spaceChars)
475+ finalSpace <- many (oneOf spaceChars)
522− finalNewlines <- many newline
476+ finalNewlines <- many newline
523− return (RawHtml (first ++ finalSpace ++ finalNewlines))
477+ return $ RawHtml $ first ++ finalSpace ++ finalNewlines
524 else rawHtmlBlocks
478 else rawHtmlBlocks
525
479
526-- True if tag is self-closing
480-- True if tag is self-closing
527isSelfClosing tag =
481isSelfClosing tag =
528 isSuffixOf "/>" $ filter (\c -> (not (c `elem` " \n\t"))) tag
482 isSuffixOf "/>" $ filter (\c -> (not (c `elem` " \n\t"))) tag
529
483
530−strictHtmlBlock = try (do
484+strictHtmlBlock = try $ do
531 tag <- anyHtmlBlockTag
485 tag <- anyHtmlBlockTag
532 let tag' = extractTagType tag
486 let tag' = extractTagType tag
533 if isSelfClosing tag || tag' == "hr"
487 if isSelfClosing tag || tag' == "hr"
534 then return tag
488 then return tag
535− else do
536− contents <- many (do{notFollowedBy' (htmlEndTag tag');
489+ else do contents <- many (notFollowedBy' (htmlEndTag tag') >>
537− htmlElement <|> (count 1 anyChar)})
490+ (htmlElement <|> (count 1 anyChar)))
538− end <- htmlEndTag tag'
491+ end <- htmlEndTag tag'
539− return $ tag ++ (concat contents) ++ end)
492+ return $ tag ++ concat contents ++ end
540
493
541−rawHtmlBlocks = try (do
494+rawHtmlBlocks = try $ do
542 htmlBlocks <- many1 rawHtmlBlock
495 htmlBlocks <- many1 rawHtmlBlock
543 let combined = concatMap (\(RawHtml str) -> str) htmlBlocks
496 let combined = concatMap (\(RawHtml str) -> str) htmlBlocks
544− let combined' = if (last combined == '\n')
497+ let combined' = if not (null combined) && last combined == '\n'
545 then init combined -- strip extra newline
498 then init combined -- strip extra newline
546 else combined
499 else combined
547− return (RawHtml combined'))
500+ return $ RawHtml combined'
548
501
549--
502--
550-- LaTeX
503-- LaTeX
551--
504--
552
505
553−rawLaTeXEnvironment' = do
554− failIfStrict
555− rawLaTeXEnvironment
506+rawLaTeXEnvironment' = failIfStrict >> rawLaTeXEnvironment
556
507
557--
508--
558-- Tables
509-- Tables
@@ -560,54 +511,46 @@ rawLaTeXEnvironment' = do
560
511
561-- Parse a dashed line with optional trailing spaces; return its length
512-- Parse a dashed line with optional trailing spaces; return its length
562-- and the length including trailing space.
513-- and the length including trailing space.
563−dashedLine ch = do
514+dashedLine ch = try $ do
564− dashes <- many1 (char ch)
515+ dashes <- many1 (char ch)
565− sp <- many spaceChar
516+ sp <- many spaceChar
566− return $ (length dashes, length $ dashes ++ sp)
517+ return $ (length dashes, length $ dashes ++ sp)
567
518
568-- Parse a table header with dashed lines of '-' preceded by
519-- Parse a table header with dashed lines of '-' preceded by
569-- one line of text.
520-- one line of text.
570−simpleTableHeader = do
521+simpleTableHeader = try $ do
571− rawContent <- anyLine
522+ rawContent <- anyLine
572− initSp <- nonindentSpaces
523+ initSp <- nonindentSpaces
573− dashes <- many1 (dashedLine '-')
524+ dashes <- many1 (dashedLine '-')
574− newline
525+ newline
575− let (lengths, lines) = unzip dashes
526+ let (lengths, lines) = unzip dashes
576− let indices = scanl (+) (length initSp) lines
527+ let indices = scanl (+) (length initSp) lines
577− let rawHeads = tail $ splitByIndices (init indices) rawContent
528+ let rawHeads = tail $ splitByIndices (init indices) rawContent
578− let aligns = zipWith alignType (map (\a -> [a]) rawHeads) lengths
529+ let aligns = zipWith alignType (map (\a -> [a]) rawHeads) lengths
579− return $ (rawHeads, aligns, indices)
530+ return (rawHeads, aligns, indices)
580
531
581-- Parse a table footer - dashed lines followed by blank line.
532-- Parse a table footer - dashed lines followed by blank line.
582−tableFooter = try $ do
583− nonindentSpaces
533+tableFooter = try $ nonindentSpaces >> many1 (dashedLine '-') >> blanklines
584− many1 (dashedLine '-')
585− blanklines
586
534
587-- Parse a table separator - dashed line.
535-- Parse a table separator - dashed line.
588−tableSep = try $ do
589− nonindentSpaces
536+tableSep = try $ nonindentSpaces >> many1 (dashedLine '-') >> string "\n"
590− many1 (dashedLine '-')
591− string "\n"
592
537
593-- Parse a raw line and split it into chunks by indices.
538-- Parse a raw line and split it into chunks by indices.
594rawTableLine indices = do
539rawTableLine indices = do
595− notFollowedBy' (blanklines <|> tableFooter)
540+ notFollowedBy' (blanklines <|> tableFooter)
596− line <- many1Till anyChar newline
541+ line <- many1Till anyChar newline
597− return $ map removeLeadingTrailingSpace $ tail $
542+ return $ map removeLeadingTrailingSpace $ tail $
598− splitByIndices (init indices) line
543+ splitByIndices (init indices) line
599
544
600-- Parse a table line and return a list of lists of blocks (columns).
545-- 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))
604
547
605-- Parse a multiline table row and return a list of blocks (columns).
548-- Parse a multiline table row and return a list of blocks (columns).
606multilineRow indices = try $ do
549multilineRow indices = try $ do
607− colLines <- many1 (rawTableLine indices)
550+ colLines <- many1 (rawTableLine indices)
608− option "" blanklines
551+ optional blanklines
609− let cols = map unlines $ transpose colLines
552+ let cols = map unlines $ transpose colLines
610− mapM (parseFromString (many plain)) cols
553+ mapM (parseFromString (many plain)) cols
611
554
612-- Calculate relative widths of table columns, based on indices
555-- Calculate relative widths of table columns, based on indices
613widthsFromIndices :: Int -- Number of columns on terminal
556widthsFromIndices :: Int -- Number of columns on terminal
@@ -615,22 +558,22 @@ widthsFromIndices :: Int -- Number of columns on terminal
615 -> [Float] -- Fractional relative sizes of columns
558 -> [Float] -- Fractional relative sizes of columns
616widthsFromIndices _ [] = []
559widthsFromIndices _ [] = []
617widthsFromIndices numColumns indices =
560widthsFromIndices numColumns indices =
618− let lengths = zipWith (-) indices (0:indices)
561+ let lengths = zipWith (-) indices (0:indices)
619− totLength = sum lengths
562+ totLength = sum lengths
620− quotient = if totLength > numColumns
563+ quotient = if totLength > numColumns
621− then fromIntegral totLength
564+ then fromIntegral totLength
622− else fromIntegral numColumns
565+ else fromIntegral numColumns
623− fracs = map (\l -> (fromIntegral l) / quotient) lengths in
566+ fracs = map (\l -> (fromIntegral l) / quotient) lengths in
624− tail fracs
567+ tail fracs
625
568
626-- Parses a table caption: inlines beginning with 'Table:'
569-- Parses a table caption: inlines beginning with 'Table:'
627-- and followed by blank lines.
570-- and followed by blank lines.
628tableCaption = try $ do
571tableCaption = try $ do
629− nonindentSpaces
572+ nonindentSpaces
630− string "Table:"
573+ string "Table:"
631− result <- many1 inline
574+ result <- many1 inline
632− blanklines
575+ blanklines
633− return $ normalizeSpaces result
576+ return $ normalizeSpaces result
634
577
635-- Parse a table using 'headerParser', 'lineParser', and 'footerParser'.
578-- Parse a table using 'headerParser', 'lineParser', and 'footerParser'.
636tableWith headerParser lineParser footerParser = try $ do
579tableWith headerParser lineParser footerParser = try $ do
@@ -653,30 +596,19 @@ simpleTable = tableWith simpleTableHeader tableLine blanklines
653multilineTable = tableWith multilineTableHeader multilineRow tableFooter
596multilineTable = tableWith multilineTableHeader multilineRow tableFooter
654
597
655multilineTableHeader = try $ do
598multilineTableHeader = try $ do
656− tableSep
599+ tableSep
657− rawContent <- many1 (do{notFollowedBy' tableSep;
600+ rawContent <- many1 (notFollowedBy' tableSep >> many1Till anyChar newline)
658− many1Till anyChar newline})
659− initSp <- nonindentSpaces
601+ initSp <- nonindentSpaces
660− dashes <- many1 (dashedLine '-')
602+ dashes <- many1 (dashedLine '-')
661− newline
603+ newline
662− let (lengths, lines) = unzip dashes
604+ let (lengths, lines) = unzip dashes
663− let indices = scanl (+) (length initSp) lines
605+ let indices = scanl (+) (length initSp) lines
664− let rawHeadsList = transpose $ map
606+ let rawHeadsList = transpose $ map
665− (\ln -> tail $ splitByIndices (init indices) ln)
607+ (\ln -> tail $ splitByIndices (init indices) ln)
666− rawContent
608+ rawContent
667− let rawHeads = map (joinWithSep " ") rawHeadsList
609+ let rawHeads = map (joinWithSep " ") rawHeadsList
668− let aligns = zipWith alignType rawHeadsList lengths
610+ let aligns = zipWith alignType rawHeadsList lengths
669− return $ ((map removeLeadingTrailingSpace rawHeads),
611+ return ((map removeLeadingTrailingSpace rawHeads), aligns, indices)
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
680
612
681-- Returns an alignment type for a table, based on a list of strings
613-- Returns an alignment type for a table, based on a list of strings
682-- (the rows of the column header) and a number (the length of the
614-- (the rows of the column header) and a number (the length of the
@@ -684,19 +616,17 @@ longest (x:xs) =
684alignType :: [String] -> Int -> Alignment
616alignType :: [String] -> Int -> Alignment
685alignType [] len = AlignDefault
617alignType [] len = AlignDefault
686alignType strLst len =
618alignType strLst len =
619+ let str = head $ sortBy (comparing length) $
687− let str = longest $ map removeTrailingSpace strLst
620+ map removeTrailingSpace strLst
688− leftSpace = if null str then False else ((str !! 0) `elem` " \t")
621+ leftSpace = if null str then False else (str !! 0) `elem` " \t"
689− rightSpace = (length str < len || (str !! (len - 1)) `elem` " \t") in
622+ rightSpace = length str < len || (str !! (len - 1)) `elem` " \t"
690− case (leftSpace, rightSpace) of
623+ in case (leftSpace, rightSpace) of
691 (True, False) -> AlignRight
624 (True, False) -> AlignRight
692 (False, True) -> AlignLeft
625 (False, True) -> AlignLeft
693− (True, True) -> AlignCenter
626+ (True, True) -> AlignCenter
694 (False, False) -> AlignDefault
627 (False, False) -> AlignDefault
695
628
696−table = do
697− failIfStrict
698− result <- simpleTable <|> multilineTable <?> "table"
629+table = failIfStrict >> (simpleTable <|> multilineTable) <?> "table"
699− return result
700
630
701--
631--
702-- inline
632-- inline
@@ -704,7 +634,7 @@ table = do
704
634
705inline = choice [ rawLaTeXInline'
635inline = choice [ rawLaTeXInline'
706 , escapedChar
636 , escapedChar
707− , entity
637+ , charRef
708 , note
638 , note
709 , inlineNote
639 , inlineNote
710 , link
640 , link
@@ -734,80 +664,64 @@ escapedChar = try $ do
734 result <- if stateStrict state
664 result <- if stateStrict state
735 then oneOf "\\`*_{}[]()>#+-.!~"
665 then oneOf "\\`*_{}[]()>#+-.!~"
736 else satisfy (not . isAlphaNum)
666 else satisfy (not . isAlphaNum)
737− return (Str [result])
667+ return $ Str [result]
738
668
739−ltSign = try (do
669+ltSign = try $ do
740 notFollowedBy (noneOf "<") -- continue only if it's a <
670 notFollowedBy (noneOf "<") -- continue only if it's a <
741 notFollowedBy' rawHtmlBlocks -- don't return < if it starts html
671 notFollowedBy' rawHtmlBlocks -- don't return < if it starts html
742 char '<'
672 char '<'
743− return (Str ['<']))
673+ return $ Str ['<']
744
674
745specialCharsMinusLt = filter (/= '<') specialChars
675specialCharsMinusLt = filter (/= '<') specialChars
746
676
747symbol = do
677symbol = do
748 result <- oneOf specialCharsMinusLt
678 result <- oneOf specialCharsMinusLt
749− return (Str [result])
679+ return $ Str [result]
750
680
751-- parses inline code, between n `s and n `s
681-- parses inline code, between n `s and n `s
752−code = try (do
682+code = try $ do
753 starts <- many1 (char '`')
683 starts <- many1 (char '`')
754 let num = length starts
684 let num = length starts
755 result <- many1Till anyChar (try (count num (char '`')))
685 result <- many1Till anyChar (try (count num (char '`')))
756 -- get rid of any internal newlines
686 -- get rid of any internal newlines
757− let result' = removeLeadingTrailingSpace $ joinWithSep " " $ lines result
687+ return $ Code $ removeLeadingTrailingSpace $ joinWithSep " " $ lines result
758− return (Code result'))
759
688
760−mathWord = many1 (choice [ (noneOf " \t\n\\$"),
689+mathWord = many1 ((noneOf " \t\n\\$") <|>
761− (try (do
762− c <- char '\\'
763− notFollowedBy (char '$')
690+ (try (char '\\') >>~ notFollowedBy (char '$')))
764− return c))])
765
691
766−math = try (do
692+math = try $ do
767 failIfStrict
693 failIfStrict
768 char '$'
694 char '$'
769 notFollowedBy space
695 notFollowedBy space
770 words <- sepBy1 mathWord (many1 space)
696 words <- sepBy1 mathWord (many1 space)
771 char '$'
697 char '$'
772− return (TeX ("$" ++ (joinWithSep " " words) ++ "$")))
698+ return $ TeX ("$" ++ (joinWithSep " " words) ++ "$")
773
699
774−emph = do
775− result <- choice [ (enclosed (char '*') (char '*') inline),
700+emph = ((enclosed (char '*') (char '*') inline) <|>
776− (enclosed (char '_') (char '_') inline) ]
701+ (enclosed (char '_') (char '_') inline)) >>=
777− return $ Emph (normalizeSpaces result)
702+ return . Emph . normalizeSpaces
778
703
779−strong = do
780− result <- (enclosed (string "**") (string "**") inline) <|>
704+strong = ((enclosed (string "**") (string "**") inline) <|>
781− (enclosed (string "__") (string "__") inline)
705+ (enclosed (string "__") (string "__") inline)) >>=
782− return $ Strong (normalizeSpaces result)
706+ return . Strong . normalizeSpaces
783
707
784−strikeout = do
785− failIfStrict
786− result <- enclosed (string "~~") (string "~~") inline
708+strikeout = failIfStrict >> enclosed (string "~~") (string "~~") inline >>=
787− return $ Strikeout (normalizeSpaces result)
709+ return . Strikeout . normalizeSpaces
788
710
789−superscript = do
790− failIfStrict
791− result <- enclosed (char '^') (char '^')
711+superscript = failIfStrict >> enclosed (char '^') (char '^')
792− (notFollowedBy' whitespace >> inline) -- may not contain Space
712+ (notFollowedBy' whitespace >> inline) >>= -- may not contain Space
793− return $ Superscript result
713+ return . Superscript
794
714
795−subscript = do
796− failIfStrict
797− result <- enclosed (char '~') (char '~')
715+subscript = failIfStrict >> enclosed (char '~') (char '~')
798− (notFollowedBy' whitespace >> inline) -- may not contain Space
716+ (notFollowedBy' whitespace >> inline) >>= -- may not contain Space
799− return $ Subscript result
717+ return . Subscript
800
718
801−smartPunctuation = do
719+smartPunctuation = failUnlessSmart >>
802− failUnlessSmart
803− choice [ quoted, apostrophe, dash, ellipses ]
720+ choice [ quoted, apostrophe, dash, ellipses ]
804
721
805−apostrophe = do
806− char '\'' <|> char '\8217'
807− return Apostrophe
722+apostrophe = (char '\'' <|> char '\8217') >> return Apostrophe
808
723
809−quoted = do
810− doubleQuoted <|> singleQuoted
724+quoted = doubleQuoted <|> singleQuoted
811
725
812withQuoteContext context parser = do
726withQuoteContext context parser = do
813 oldState <- getState
727 oldState <- getState
@@ -820,15 +734,13 @@ withQuoteContext context parser = do
820
734
821singleQuoted = try $ do
735singleQuoted = try $ do
822 singleQuoteStart
736 singleQuoteStart
823− withQuoteContext InSingleQuote $ do
737+ withQuoteContext InSingleQuote $ many1Till inline singleQuoteEnd >>=
824− result <- many1Till inline singleQuoteEnd
825− return $ Quoted SingleQuote $ normalizeSpaces result
738+ return . Quoted SingleQuote . normalizeSpaces
826
739
827doubleQuoted = try $ do
740doubleQuoted = try $ do
828 doubleQuoteStart
741 doubleQuoteStart
829− withQuoteContext InDoubleQuote $ do
742+ withQuoteContext InDoubleQuote $ many1Till inline doubleQuoteEnd >>=
830− result <- many1Till inline doubleQuoteEnd
831− return $ Quoted DoubleQuote $ normalizeSpaces result
743+ return . Quoted DoubleQuote . normalizeSpaces
832
744
833failIfInQuoteContext context = do
745failIfInQuoteContext context = do
834 st <- getState
746 st <- getState
@@ -836,88 +748,65 @@ failIfInQuoteContext context = do
836 then fail "already inside quotes"
748 then fail "already inside quotes"
837 else return ()
749 else return ()
838
750
839−singleQuoteStart = try $ do
751+singleQuoteStart = do
840 failIfInQuoteContext InSingleQuote
752 failIfInQuoteContext InSingleQuote
841− char '\8216' <|> do
753+ char '\8216' <|>
842− char '\''
754+ do char '\''
843− notFollowedBy (oneOf ")!],.;:-? \t\n")
755+ notFollowedBy (oneOf ")!],.;:-? \t\n")
844− notFollowedBy (try (do -- possessive or contraction
845− oneOfStrings ["s","t","m","ve","ll","re"]
756+ notFollowedBy (try (oneOfStrings ["s","t","m","ve","ll","re"] >>
846− satisfy (not . isAlphaNum)))
757+ satisfy (not . isAlphaNum))) -- possess/contraction
847− return '\''
758+ return '\''
848−
759+
849−singleQuoteEnd = try $ do
850− char '\'' <|> char '\8217'
851− notFollowedBy alphaNum
760+singleQuoteEnd = (char '\'' <|> char '\8217') >> notFollowedBy alphaNum
852−
761+
853−doubleQuoteStart = try $ do
854− failIfInQuoteContext InDoubleQuote
762+doubleQuoteStart = failIfInQuoteContext InDoubleQuote >>
855− char '"' <|> char '\8220'
763+ (char '"' <|> char '\8220') >>
856− notFollowedBy (oneOf " \t\n")
764+ notFollowedBy (oneOf " \t\n")
857
765
858doubleQuoteEnd = char '"' <|> char '\8221'
766doubleQuoteEnd = char '"' <|> char '\8221'
859
767
860−ellipses = try (do
861− oneOfStrings ["...", " . . . ", ". . .", " . . ."]
768+ellipses = try $ oneOfStrings ["...", " . . . ", ". . .", " . . ."] >>
862− return Ellipses)
769+ return Ellipses
863
770
864dash = enDash <|> emDash
771dash = enDash <|> emDash
865
772
866−enDash = try (do
867− char '-'
868− notFollowedBy (noneOf "0123456789")
773+enDash = try $ char '-' >> notFollowedBy (noneOf "0123456789") >> return EnDash
869− return EnDash)
870
774
871−emDash = try (do
872− skipSpaces
873− oneOfStrings ["---", "--"]
775+emDash = try $ skipSpaces >> oneOfStrings ["---", "--"] >>
874− skipSpaces
875− return EmDash)
776+ skipSpaces >> return EmDash
876
777
877−whitespace = do
878− many1 (oneOf spaceChars) <?> "whitespace"
778+whitespace = (many1 (oneOf spaceChars) >> return Space) <?> "whitespace"
879− return Space
880
779
881−tabchar = do
882− tab
883− return (Str "\t")
780+tabchar = tab >> return (Str "\t")
884
781
885-- hard line break
782-- hard line break
886−linebreak = try (do
887− oneOf spaceChars
888− many1 (oneOf spaceChars)
783+linebreak = try $ oneOf spaceChars >> many1 (oneOf spaceChars) >>
889− endline
890− return LineBreak )
784+ endline >> return LineBreak
891
785
892nonEndline = satisfy (/='\n')
786nonEndline = satisfy (/='\n')
893
787
894−entity = do
895− ent <- characterEntity
896− return $ Str [ent]
897−
898strChar = noneOf (specialChars ++ spaceChars ++ "\n")
788strChar = noneOf (specialChars ++ spaceChars ++ "\n")
899
789
900−str = do
901− result <- many1 strChar
790+str = many1 strChar >>= return . Str
902− return (Str result)
903
791
904-- an endline character that can be treated as a space, not a structural break
792-- an endline character that can be treated as a space, not a structural break
905−endline = try (do
793+endline = try $ do
906 newline
794 newline
907 notFollowedBy blankline
795 notFollowedBy blankline
908 st <- getState
796 st <- getState
909 if stateStrict st
797 if stateStrict st
910 then do
798 then do
911− notFollowedBy' emailBlockQuoteStart
799+ notFollowedBy emailBlockQuoteStart
912 notFollowedBy (char '#') -- atx header
800 notFollowedBy (char '#') -- atx header
913− notFollowedBy (try (do{manyTill anyChar newline;
801+ notFollowedBy (manyTill anyChar newline >> oneOf setextHChars)
914− oneOf setextHChars})) -- setext header
802+ -- setext header
915 else return ()
803 else return ()
916 -- parse potential list-starts differently if in a list:
804 -- parse potential list-starts differently if in a list:
917− if (stateParserContext st) == ListItemState
805+ if stateParserContext st == ListItemState
918− then notFollowedBy' $ choice [bulletListStart, anyOrderedListStart >> return ()]
806+ then notFollowedBy' (bulletListStart <|>
807+ (anyOrderedListStart >> return ()))
919 else return ()
808 else return ()
920− return Space)
809+ return Space
921
810
922--
811--
923-- links
812-- links
@@ -930,24 +819,23 @@ reference = notFollowedBy' (string "[^") >> -- footnote reference
930-- source for a link, with optional title
819-- source for a link, with optional title
931source = try $ do
820source = try $ do
932 char '('
821 char '('
933− option ' ' (char '<')
822+ optional (char '<')
934 src <- many (noneOf ")> \t\n")
823 src <- many (noneOf ")> \t\n")
935− option ' ' (char '>')
824+ optional (char '>')
936 tit <- option "" title
825 tit <- option "" title
937 skipSpaces
826 skipSpaces
938 char ')'
827 char ')'
939 return (removeTrailingSpace src, tit)
828 return (removeTrailingSpace src, tit)
940
829
941−titleWith startChar endChar = try (do
830+titleWith startChar endChar = try $ do
942 leadingSpace <- many1 (oneOf " \t\n")
831 leadingSpace <- many1 (oneOf " \t\n")
943 if length (filter (=='\n') leadingSpace) > 1
832 if length (filter (=='\n') leadingSpace) > 1
944 then fail "title must be separated by space and on same or next line"
833 then fail "title must be separated by space and on same or next line"
945 else return ()
834 else return ()
946 char startChar
835 char startChar
947− tit <- manyTill anyChar (try (do char endChar
836+ tit <- manyTill anyChar (try (char endChar >> skipSpaces >>
948− skipSpaces
949− notFollowedBy (noneOf ")\n")))
837+ notFollowedBy (noneOf ")\n")))
950− return $ decodeEntities tit)
838+ return $ decodeCharacterReferences tit
951
839
952title = choice [ titleWith '(' ')',
840title = choice [ titleWith '(' ')',
953 titleWith '"' '"',
841 titleWith '"' '"',
@@ -955,22 +843,20 @@ title = choice [ titleWith '(' ')',
955
843
956link = choice [explicitLink, referenceLink] <?> "link"
844link = choice [explicitLink, referenceLink] <?> "link"
957
845
958−explicitLink = try (do
846+explicitLink = try $ do
959 label <- reference
847 label <- reference
960 src <- source
848 src <- source
961− return (Link label src))
849+ return $ Link label src
962
850
963-- a link like [this][ref] or [this][] or [this]
851-- a link like [this][ref] or [this][] or [this]
964referenceLink = try $ do
852referenceLink = try $ do
965 label <- reference
853 label <- reference
966− ref <- option [] (try (do skipSpaces
854+ ref <- option [] (try (skipSpaces >> optional newline >>
967− option ' ' newline
968− skipSpaces
855+ skipSpaces >> reference))
969− reference))
970 let ref' = if null ref then label else ref
856 let ref' = if null ref then label else ref
971 state <- getState
857 state <- getState
972 case lookupKeySrc (stateKeys state) ref' of
858 case lookupKeySrc (stateKeys state) ref' of
973− Nothing -> fail "no corresponding key"
859+ Nothing -> fail "no corresponding key"
974 Just target -> return (Link label target)
860 Just target -> return (Link label target)
975
861
976autoLink = autoLinkEmail <|> autoLinkRegular
862autoLink = autoLinkEmail <|> autoLinkRegular
@@ -992,10 +878,10 @@ autoLinkRegular = try $ do
992 let src = prot ++ rest
878 let src = prot ++ rest
993 return $ Link [Code src] (src, "")
879 return $ Link [Code src] (src, "")
994
880
995−image = try (do
881+image = try $ do
996 char '!'
882 char '!'
997 (Link label src) <- link
883 (Link label src) <- link
998− return (Image label src))
884+ return $ Image label src
999
885
1000note = try $ do
886note = try $ do
1001 failIfStrict
887 failIfStrict
@@ -1003,23 +889,21 @@ note = try $ do
1003 state <- getState
889 state <- getState
1004 let notes = stateNotes state
890 let notes = stateNotes state
1005 case lookup ref notes of
891 case lookup ref notes of
1006− Nothing -> fail "note not found"
892+ Nothing -> fail "note not found"
1007− Just contents -> return (Note contents)
893+ Just contents -> return $ Note contents
1008
894
1009inlineNote = try $ do
895inlineNote = try $ do
1010 failIfStrict
896 failIfStrict
1011 char '^'
897 char '^'
1012 contents <- inlinesInBalanced "[" "]"
898 contents <- inlinesInBalanced "[" "]"
1013− return (Note [Para contents])
899+ return $ Note [Para contents]
1014
900
1015−rawLaTeXInline' = do
1016− failIfStrict
1017− rawLaTeXInline
901+rawLaTeXInline' = failIfStrict >> rawLaTeXInline
1018
902
1019rawHtmlInline' = do
903rawHtmlInline' = do
1020 st <- getState
904 st <- getState
1021− result <- if stateStrict st
905+ result <- choice $ if stateStrict st
1022− then choice [htmlBlockElement, anyHtmlTag, anyHtmlEndTag]
906+ then [htmlBlockElement, anyHtmlTag, anyHtmlEndTag]
1023− else choice [htmlBlockElement, anyHtmlInlineTag]
907+ else [htmlBlockElement, anyHtmlInlineTag]
1024− return (HtmlInline result)
908+ return $ HtmlInline result
1025
909
src/Text/Pandoc/Readers/RST.hs+147−174
@@ -31,23 +31,14 @@ module Text.Pandoc.Readers.RST (
31 readRST
31 readRST
32 ) where
32 ) where
33import Text.Pandoc.Definition
33import Text.Pandoc.Definition
34−import Text.Pandoc.ParserCombinators
35import Text.Pandoc.Shared
34import Text.Pandoc.Shared
36−import Text.Pandoc.Readers.HTML ( anyHtmlBlockTag, anyHtmlInlineTag )
37−import Text.Regex ( matchRegex, mkRegex )
38import Text.ParserCombinators.Parsec
35import Text.ParserCombinators.Parsec
39−import Data.Maybe ( fromMaybe )
40import Data.List ( findIndex, delete )
36import Data.List ( findIndex, delete )
41−import Data.Char ( toUpper )
42
37
43-- | Parse reStructuredText string and return Pandoc document.
38-- | Parse reStructuredText string and return Pandoc document.
44readRST :: ParserState -> String -> Pandoc
39readRST :: ParserState -> String -> Pandoc
45readRST state str = (readWith parseRST) state (str ++ "\n\n")
40readRST state str = (readWith parseRST) state (str ++ "\n\n")
46
41
47−-- | Parse a string and print result (for testing).
48−testString :: String -> IO ()
49−testString = testStringWith parseRST
50−
51--
42--
52-- Constants and data structure definitions
43-- Constants and data structure definitions
53---
44---
@@ -62,15 +53,11 @@ specialChars = "\\`|*_<>$:[-"
62-- parsing documents
53-- parsing documents
63--
54--
64
55
65−isAnonKey (ref, src) = (ref == [Str "_"])
56+isAnonKey (ref, src) = ref == [Str "_"]
66−
67−isHeader1 :: Block -> Bool
68−isHeader1 (Header 1 _) = True
69−isHeader1 _ = False
70
57
71−isHeader2 :: Block -> Bool
58+isHeader :: Int -> Block -> Bool
72−isHeader2 (Header 2 _) = True
59+isHeader n (Header x _) = x == n
73−isHeader2 _ = False
60+isHeader _ _ = False
74
61
75-- | Promote all headers in a list of blocks. (Part of
62-- | Promote all headers in a list of blocks. (Part of
76-- title transformation for RST.)
63-- title transformation for RST.)
@@ -86,23 +73,23 @@ promoteHeaders num [] = []
86titleTransform :: [Block] -- ^ list of blocks
73titleTransform :: [Block] -- ^ list of blocks
87 -> ([Block], [Inline]) -- ^ modified list of blocks, title
74 -> ([Block], [Inline]) -- ^ modified list of blocks, title
88titleTransform ((Header 1 head1):(Header 2 head2):rest) = -- title subtitle
75titleTransform ((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)
90 then ((Header 1 head1):(Header 2 head2):rest, [])
77 then ((Header 1 head1):(Header 2 head2):rest, [])
91 else ((promoteHeaders 2 rest), head1 ++ [Str ":", Space] ++ head2)
78 else ((promoteHeaders 2 rest), head1 ++ [Str ":", Space] ++ head2)
92titleTransform ((Header 1 head1):rest) = -- title, no subtitle
79titleTransform ((Header 1 head1):rest) = -- title, no subtitle
93− if (any isHeader1 rest)
80+ if (any (isHeader 1) rest)
94 then ((Header 1 head1):rest, [])
81 then ((Header 1 head1):rest, [])
95 else ((promoteHeaders 1 rest), head1)
82 else ((promoteHeaders 1 rest), head1)
96titleTransform blocks = (blocks, [])
83titleTransform blocks = (blocks, [])
97
84
98parseRST = do
85parseRST = do
99− -- first pass: get anonymous keys
86+ -- first pass: get keys
100− refs <- manyTill (referenceKey <|> (do l <- lineClump
87+ refs <- manyTill (referenceKey <|> (lineClump >>= return . LineClump)) eof
101− return (LineClump l))) eof
102 let keys = map (\(KeyBlock label target) -> (label, target)) $
88 let keys = map (\(KeyBlock label target) -> (label, target)) $
103 filter isKeyBlock refs
89 filter isKeyBlock refs
90+ -- second pass, with keys stripped out
104 let rawlines = map (\(LineClump ln) -> ln) $ filter isLineClump refs
91 let rawlines = map (\(LineClump ln) -> ln) $ filter isLineClump refs
105− setInput $ concat rawlines -- with keys stripped out
92+ setInput $ concat rawlines
106 updateState (\state -> state { stateKeys = keys })
93 updateState (\state -> state { stateKeys = keys })
107 blocks <- parseBlocks
94 blocks <- parseBlocks
108 let blocks' = filter (/= Null) blocks
95 let blocks' = filter (/= Null) blocks
@@ -113,7 +100,7 @@ parseRST = do
113 let authors = stateAuthors state
100 let authors = stateAuthors state
114 let date = stateDate state
101 let date = stateDate state
115 let title' = if (null title) then (stateTitle state) else title
102 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''
117
104
118--
105--
119-- parsing blocks
106-- parsing blocks
@@ -121,32 +108,39 @@ parseRST = do
121
108
122parseBlocks = manyTill block eof
109parseBlocks = manyTill block eof
123
110
124−block = choice [ codeBlock, rawHtmlBlock, rawLaTeXBlock, blockQuote,
111+block = choice [ codeBlock
112+ , rawHtmlBlock
113+ , rawLaTeXBlock
114+ , blockQuote
115+ , imageBlock
125− imageBlock, unknownDirective, header,
116+ , unknownDirective
117+ , header
118+ , hrule
119+ , list
126− hrule, list, fieldList, lineBlock, para, plain,
120+ , fieldList
121+ , lineBlock
122+ , para
123+ , plain
127− nullBlock ] <?> "block"
124+ , nullBlock ] <?> "block"
128
125
129--
126--
130-- field list
127-- field list
131--
128--
132
129
133−fieldListItem = try (do
130+fieldListItem = try $ do
134 char ':'
131 char ':'
135 name <- many1 alphaNum
132 name <- many1 alphaNum
136 string ": "
133 string ": "
137 skipSpaces
134 skipSpaces
138 first <- manyTill anyChar newline
135 first <- manyTill anyChar newline
139− rest <- many (do
140− notFollowedBy (char ':')
136+ rest <- many (notFollowedBy ((char ':') <|> blankline) >>
141− notFollowedBy blankline
142− skipSpaces
143− manyTill anyChar newline )
137+ skipSpaces >> manyTill anyChar newline)
144− return (name, (joinWithSep " " (first:rest))))
138+ return $ (name, (joinWithSep " " (first:rest)))
145−
139+
146−fieldList = try (do
140+fieldList = try $ do
147 items <- many1 fieldListItem
141 items <- many1 fieldListItem
148 blanklines
142 blanklines
149− let authors = case (lookup "Authors" items) of
143+ let authors = case lookup "Authors" items of
150 Just auth -> [auth]
144 Just auth -> [auth]
151 Nothing -> map snd (filter (\(x,y) -> x == "Author") items)
145 Nothing -> map snd (filter (\(x,y) -> x == "Author") items)
152 let date = case (lookup "Date" items) of
146 let date = case (lookup "Date" items) of
@@ -162,82 +156,74 @@ fieldList = try (do
162 updateState (\st -> st { stateAuthors = authors,
156 updateState (\st -> st { stateAuthors = authors,
163 stateDate = date,
157 stateDate = date,
164 stateTitle = title })
158 stateTitle = title })
165− return (BlockQuote result))
159+ return $ BlockQuote result
166
160
167--
161--
168-- line block
162-- line block
169--
163--
170
164
171−lineBlockLine = try (do
165+lineBlockLine = try $ do
172 string "| "
166 string "| "
173 white <- many (oneOf " \t")
167 white <- many (oneOf " \t")
174 line <- manyTill inline newline
168 line <- manyTill inline newline
175− let line' = (if null white then [] else [Str white]) ++ line ++ [LineBreak]
169+ return $ (if null white then [] else [Str white]) ++ line ++ [LineBreak]
176− return line')
177
170
178−lineBlock = try (do
171+lineBlock = try $ do
179 lines <- many1 lineBlockLine
172 lines <- many1 lineBlockLine
180 blanklines
173 blanklines
181− return $ Para (concat lines))
174+ return $ Para (concat lines)
182
175
183--
176--
184-- paragraph block
177-- paragraph block
185--
178--
186
179
187−para = choice [ paraBeforeCodeBlock, paraNormal ] <?> "paragraph"
180+para = paraBeforeCodeBlock <|> paraNormal <?> "paragraph"
188
181
189−codeBlockStart = try (do
182+codeBlockStart = try $ string "::" >> blankline >> blankline
190− string "::"
191− blankline
192− blankline)
193
183
194-- paragraph that ends in a :: starting a code block
184-- paragraph that ends in a :: starting a code block
195−paraBeforeCodeBlock = try (do
185+paraBeforeCodeBlock = try $ do
196− result <- many1 (do {notFollowedBy' codeBlockStart; inline})
186+ result <- many1 (notFollowedBy' codeBlockStart >> inline)
197 lookAhead (string "::")
187 lookAhead (string "::")
198− return (Para (if (last result == Space)
188+ return $ Para $ if last result == Space
199− then normalizeSpaces result
189+ then normalizeSpaces result
200− else (normalizeSpaces result) ++ [Str ":"])))
190+ else (normalizeSpaces result) ++ [Str ":"]
201
191
202-- regular paragraph
192-- regular paragraph
203−paraNormal = try (do
193+paraNormal = try $ do
204 result <- many1 inline
194 result <- many1 inline
205 newline
195 newline
206 blanklines
196 blanklines
207− let result' = normalizeSpaces result
208− return (Para result'))
197+ return $ Para $ normalizeSpaces result
209
198
210−plain = do
199+plain = many1 inline >>= return . Plain . normalizeSpaces
211− result <- many1 inline
212− let result' = normalizeSpaces result
213− return (Plain result')
214
200
215--
201--
216-- image block
202-- image block
217--
203--
218
204
219−imageBlock = try (do
205+imageBlock = try $ do
220 string ".. image:: "
206 string ".. image:: "
221 src <- manyTill anyChar newline
207 src <- manyTill anyChar newline
222− return (Plain [Image [Str "image"] (src, "")]))
208+ return $ Plain [Image [Str "image"] (src, "")]
223
209
224--
210--
225-- header blocks
211-- header blocks
226--
212--
227
213
228−header = choice [ doubleHeader, singleHeader ] <?> "header"
214+header = doubleHeader <|> singleHeader <?> "header"
229
215
230-- a header with lines on top and bottom
216-- a header with lines on top and bottom
231−doubleHeader = try (do
217+doubleHeader = try $ do
232 c <- oneOf underlineChars
218 c <- oneOf underlineChars
233 rest <- many (char c) -- the top line
219 rest <- many (char c) -- the top line
234 let lenTop = length (c:rest)
220 let lenTop = length (c:rest)
235 skipSpaces
221 skipSpaces
236 newline
222 newline
237− txt <- many1 (do {notFollowedBy blankline; inline})
223+ txt <- many1 (notFollowedBy blankline >> inline)
238− pos <- getPosition
224+ pos <- getPosition
239 let len = (sourceColumn pos) - 1
225 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 ()
241 blankline -- spaces and newline
227 blankline -- spaces and newline
242 count lenTop (char c) -- the bottom line
228 count lenTop (char c) -- the bottom line
243 blanklines
229 blanklines
@@ -249,10 +235,10 @@ doubleHeader = try (do
249 Just ind -> (headerTable, ind + 1)
235 Just ind -> (headerTable, ind + 1)
250 Nothing -> (headerTable ++ [DoubleHeader c], (length headerTable) + 1)
236 Nothing -> (headerTable ++ [DoubleHeader c], (length headerTable) + 1)
251 setState (state { stateHeaderTable = headerTable' })
237 setState (state { stateHeaderTable = headerTable' })
252− return (Header level (normalizeSpaces txt)))
238+ return $ Header level (normalizeSpaces txt)
253
239
254-- a header with line on the bottom only
240-- a header with line on the bottom only
255−singleHeader = try (do
241+singleHeader = try $ do
256 notFollowedBy' whitespace
242 notFollowedBy' whitespace
257 txt <- many1 (do {notFollowedBy blankline; inline})
243 txt <- many1 (do {notFollowedBy blankline; inline})
258 pos <- getPosition
244 pos <- getPosition
@@ -268,19 +254,19 @@ singleHeader = try (do
268 Just ind -> (headerTable, ind + 1)
254 Just ind -> (headerTable, ind + 1)
269 Nothing -> (headerTable ++ [SingleHeader c], (length headerTable) + 1)
255 Nothing -> (headerTable ++ [SingleHeader c], (length headerTable) + 1)
270 setState (state { stateHeaderTable = headerTable' })
256 setState (state { stateHeaderTable = headerTable' })
271− return (Header level (normalizeSpaces txt)))
257+ return $ Header level (normalizeSpaces txt)
272
258
273--
259--
274-- hrule block
260-- hrule block
275--
261--
276
262
277−hruleWith chr = try (do
263+hruleWith chr = try $ do
278 count 4 (char chr)
264 count 4 (char chr)
279 skipMany (char chr)
265 skipMany (char chr)
280 skipSpaces
266 skipSpaces
281 newline
267 newline
282 blanklines
268 blanklines
283− return HorizontalRule)
269+ return HorizontalRule
284
270
285hrule = choice (map hruleWith underlineChars) <?> "hrule"
271hrule = choice (map hruleWith underlineChars) <?> "hrule"
286
272
@@ -289,15 +275,16 @@ hrule = choice (map hruleWith underlineChars) <?> "hrule"
289--
275--
290
276
291-- read a line indented by a given string
277-- read a line indented by a given string
292−indentedLine indents = try (do
278+indentedLine indents = try $ do
293 string indents
279 string indents
294 result <- manyTill anyChar newline
280 result <- manyTill anyChar newline
295− return (result ++ "\n"))
281+ return $ result ++ "\n"
296
282
297−-- two or more indented lines, possibly separated by blank lines
283+-- 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
284+-- if variable = True, then any indent will work, but it must be
285+-- consistent through the block.
299−-- if variable = False, indent should be one tab or equivalent in spaces
286+-- if variable = False, indent should be one tab or equivalent in spaces.
300−indentedBlock variable = try (do
287+indentedBlock variable = try $ do
301 state <- getState
288 state <- getState
302 let tabStop = stateTabStop state
289 let tabStop = stateTabStop state
303 indents <- if variable
290 indents <- if variable
@@ -305,51 +292,47 @@ indentedBlock variable = try (do
305 else oneOfStrings ["\t", (replicate tabStop ' ')]
292 else oneOfStrings ["\t", (replicate tabStop ' ')]
306 firstline <- manyTill anyChar newline
293 firstline <- manyTill anyChar newline
307 rest <- many (choice [ indentedLine indents,
294 rest <- many (choice [ indentedLine indents,
308− try (do
309− b <- blanklines
295+ try (do b <- blanklines
310− l <- indentedLine indents
296+ l <- indentedLine indents
311− return (b ++ l))])
297+ return (b ++ l))])
312− option "" blanklines
298+ optional blanklines
313− return (firstline ++ "\n" ++ (concat rest)))
299+ return $ firstline ++ "\n" ++ concat rest
314−
300+
315−codeBlock = try (do
301+codeBlock = try $ do
316 codeBlockStart
302 codeBlockStart
317 result <- indentedBlock False
303 result <- indentedBlock False
318 -- the False means we want one tab stop indent on each line
304 -- the False means we want one tab stop indent on each line
319− return (CodeBlock (stripTrailingNewlines result)))
305+ return $ CodeBlock $ stripTrailingNewlines result
320
306
321--
307--
322-- raw html
308-- raw html
323--
309--
324
310
325−rawHtmlBlock = try (do
311+rawHtmlBlock = try $ string ".. raw:: html" >> blanklines >>
326− string ".. raw:: html"
327− blanklines
328− result <- indentedBlock True
312+ indentedBlock True >>= return . RawHtml
329− return (RawHtml result))
330
313
331--
314--
332-- raw latex
315-- raw latex
333--
316--
334
317
335−rawLaTeXBlock = try (do
318+rawLaTeXBlock = try $ do
336 string ".. raw:: latex"
319 string ".. raw:: latex"
337 blanklines
320 blanklines
338 result <- indentedBlock True
321 result <- indentedBlock True
339− return (Para [(TeX result)]))
322+ return $ Para [(TeX result)]
340
323
341--
324--
342-- block quotes
325-- block quotes
343--
326--
344
327
345−blockQuote = try (do
328+blockQuote = try $ do
346 raw <- indentedBlock True
329 raw <- indentedBlock True
347 -- parse the extracted block, which may contain various block elements:
330 -- parse the extracted block, which may contain various block elements:
348 rest <- getInput
331 rest <- getInput
349 setInput $ raw ++ "\n\n"
332 setInput $ raw ++ "\n\n"
350 contents <- parseBlocks
333 contents <- parseBlocks
351 setInput rest
334 setInput rest
352− return (BlockQuote contents))
335+ return $ BlockQuote contents
353
336
354--
337--
355-- list blocks
338-- list blocks
@@ -369,15 +352,14 @@ definitionListItem = try $ do
369
352
370definitionList = try $ do
353definitionList = try $ do
371 items <- many1 definitionListItem
354 items <- many1 definitionListItem
372− return (DefinitionList items)
355+ return $ DefinitionList items
373
356
374-- parses bullet list start and returns its length (inc. following whitespace)
357-- parses bullet list start and returns its length (inc. following whitespace)
375−bulletListStart = try (do
358+bulletListStart = try $ do
376 notFollowedBy' hrule -- because hrules start out just like lists
359 notFollowedBy' hrule -- because hrules start out just like lists
377 marker <- oneOf bulletListMarkers
360 marker <- oneOf bulletListMarkers
378 white <- many1 spaceChar
361 white <- many1 spaceChar
379− let len = length (marker:white)
362+ return $ length (marker:white)
380− return len)
381
363
382-- parses ordered list start and returns its length (inc following whitespace)
364-- parses ordered list start and returns its length (inc following whitespace)
383orderedListStart style delim = try $ do
365orderedListStart style delim = try $ do
@@ -386,11 +368,11 @@ orderedListStart style delim = try $ do
386 return $ markerLen + length white
368 return $ markerLen + length white
387
369
388-- parse a line of a list item
370-- parse a line of a list item
389−listLine markerLength = try (do
371+listLine markerLength = try $ do
390 notFollowedBy blankline
372 notFollowedBy blankline
391 indentWith markerLength
373 indentWith markerLength
392 line <- manyTill anyChar newline
374 line <- manyTill anyChar newline
393− return (line ++ "\n"))
375+ return $ line ++ "\n"
394
376
395-- indent by specified number of spaces (or equiv. tabs)
377-- indent by specified number of spaces (or equiv. tabs)
396indentWith num = do
378indentWith num = do
@@ -399,7 +381,7 @@ indentWith num = do
399 if (num < tabStop)
381 if (num < tabStop)
400 then count num (char ' ')
382 then count num (char ' ')
401 else choice [ try (count num (char ' ')),
383 else choice [ try (count num (char ' ')),
402− (try (do {char '\t'; count (num - tabStop) (char ' ')})) ]
384+ (try (char '\t' >> count (num - tabStop) (char ' '))) ]
403
385
404-- parse raw text for one list item, excluding start marker and continuations
386-- parse raw text for one list item, excluding start marker and continuations
405rawListItem start = try $ do
387rawListItem start = try $ do
@@ -411,19 +393,16 @@ rawListItem start = try $ do
411-- continuation of a list item - indented and separated by blankline or
393-- continuation of a list item - indented and separated by blankline or
412-- (in compact lists) endline.
394-- (in compact lists) endline.
413-- Note: nested lists are parsed as continuations.
395-- Note: nested lists are parsed as continuations.
414−listContinuation markerLength = try (do
396+listContinuation markerLength = try $ do
415 blanks <- many1 blankline
397 blanks <- many1 blankline
416 result <- many1 (listLine markerLength)
398 result <- many1 (listLine markerLength)
417− return (blanks ++ (concat result)))
399+ return $ blanks ++ concat result
418
400
419−listItem start = try (do
401+listItem start = try $ do
420 (markerLength, first) <- rawListItem start
402 (markerLength, first) <- rawListItem start
421 rest <- many (listContinuation markerLength)
403 rest <- many (listContinuation markerLength)
422− blanks <- choice [ try (do
404+ blanks <- choice [ try (many blankline >>~ lookAhead start),
423− b <- many blankline
424− lookAhead start
425− return b),
426− many1 blankline ] -- whole list must end with blank
405+ many1 blankline ] -- whole list must end with blank.
427 -- parsing with ListItemState forces markers at beginning of lines to
406 -- parsing with ListItemState forces markers at beginning of lines to
428 -- count as list item markers, even if not separated by blank space.
407 -- count as list item markers, even if not separated by blank space.
429 -- see definition of "endline"
408 -- see definition of "endline"
@@ -436,52 +415,44 @@ listItem start = try (do
436 parsed <- parseBlocks
415 parsed <- parseBlocks
437 setInput remaining
416 setInput remaining
438 updateState (\st -> st {stateParserContext = oldContext})
417 updateState (\st -> st {stateParserContext = oldContext})
439− return parsed)
418+ return parsed
440
419
441orderedList = try $ do
420orderedList = try $ do
442 (start, style, delim) <- lookAhead anyOrderedListMarker
421 (start, style, delim) <- lookAhead anyOrderedListMarker
443 items <- many1 (listItem (orderedListStart style delim))
422 items <- many1 (listItem (orderedListStart style delim))
444 let items' = compactify items
423 let items' = compactify items
445− return (OrderedList (start, style, delim) items')
424+ return $ OrderedList (start, style, delim) items'
446
425
447−bulletList = try (do
426+bulletList = try $ do
448 items <- many1 (listItem bulletListStart)
427 items <- many1 (listItem bulletListStart)
449 let items' = compactify items
428 let items' = compactify items
450− return (BulletList items'))
429+ return $ BulletList items'
451
430
452--
431--
453-- unknown directive (e.g. comment)
432-- unknown directive (e.g. comment)
454--
433--
455
434
456−unknownDirective = try (do
435+unknownDirective = try $ do
457 string ".. "
436 string ".. "
458 manyTill anyChar newline
437 manyTill anyChar newline
459− many (do
460− string " "
461− char ':'
462− many1 (noneOf "\n:")
438+ many (string " :" >> many1 (noneOf "\n:") >> char ':' >>
463− char ':'
464− many1 (noneOf "\n")
439+ many1 (noneOf "\n") >> newline)
465− newline)
466− option "" blanklines
440+ optional blanklines
467− return Null)
441+ return Null
468
442
469--
443--
470-- reference key
444-- reference key
471--
445--
472
446
473−referenceKey = do
447+referenceKey =
474− result <- choice [imageKey, anonymousKey, regularKeyQuoted, regularKey]
448+ choice [imageKey, anonymousKey, regularKeyQuoted, regularKey] >>~
475− option "" blanklines
449+ optional blanklines
476− return result
477
450
478targetURI = try $ do
451targetURI = try $ do
479 skipSpaces
452 skipSpaces
480− option ' ' newline
453+ optional newline
481− contents <- many1 (try (do many spaceChar
454+ contents <- many1 (try (many spaceChar >> newline >>
482− newline
483− many1 spaceChar
484− noneOf " \t\n") <|> noneOf "\n")
455+ many1 spaceChar >> noneOf " \t\n") <|> noneOf "\n")
485 blanklines
456 blanklines
486 return contents
457 return contents
487
458
@@ -516,71 +487,73 @@ regularKey = try $ do
516 -- inline
487 -- inline
517 --
488 --
518
489
519−inline = choice [ superscript, subscript,
490+inline = choice [ superscript
491+ , subscript
520− escapedChar, link, image, hyphens, strong, emph, code,
492+ , escapedChar
493+ , link
494+ , image
495+ , hyphens
496+ , strong
497+ , emph
498+ , code
499+ , str
500+ , tabchar
501+ , whitespace
502+ , endline
521− str, tabchar, whitespace, endline, symbol ] <?> "inline"
503+ , symbol ] <?> "inline"
522−
504+
523−hyphens = try (do
505+hyphens = try $ do
524 result <- many1 (char '-')
506 result <- many1 (char '-')
525 option Space endline
507 option Space endline
526 -- don't want to treat endline after hyphen or dash as a space
508 -- don't want to treat endline after hyphen or dash as a space
527− return (Str result))
509+ return $ Str result
528
510
529escapedChar = escaped anyChar
511escapedChar = escaped anyChar
530
512
531symbol = do
513symbol = do
532 result <- oneOf specialChars
514 result <- oneOf specialChars
533− return (Str [result])
515+ return $ Str [result]
534
516
535-- parses inline code, between codeStart and codeEnd
517-- parses inline code, between codeStart and codeEnd
536−code = try (do
518+code = try $ do
537 string "``"
519 string "``"
538 result <- manyTill anyChar (try (string "``"))
520 result <- manyTill anyChar (try (string "``"))
539− let result' = removeLeadingTrailingSpace $ joinWithSep " " $ lines result
521+ return $ Code $ removeLeadingTrailingSpace $ joinWithSep " " $ lines result
540− return (Code result'))
541
522
542−emph = do
543− result <- enclosed (char '*') (char '*') inline
523+emph = enclosed (char '*') (char '*') inline >>=
544− return (Emph (normalizeSpaces result))
524+ return . Emph . normalizeSpaces
545
525
546−strong = do
547− result <- enclosed (string "**") (string "**") inline
526+strong = enclosed (string "**") (string "**") inline >>=
548− return (Strong (normalizeSpaces result))
527+ return . Strong . normalizeSpaces
549
528
550interpreted role = try $ do
529interpreted role = try $ do
551− option "" (try $ string "\\ ")
530+ optional $ try $ string "\\ "
552 result <- enclosed (string $ ":" ++ role ++ ":`") (char '`') anyChar
531 result <- enclosed (string $ ":" ++ role ++ ":`") (char '`') anyChar
553 nextChar <- lookAhead anyChar
532 nextChar <- lookAhead anyChar
554 try (string "\\ ") <|> lookAhead (count 1 $ oneOf " \t\n") <|> (eof >> return "")
533 try (string "\\ ") <|> lookAhead (count 1 $ oneOf " \t\n") <|> (eof >> return "")
555 return [Str result]
534 return [Str result]
556
535
557superscript = interpreted "sup" >>= (return . Superscript)
536superscript = interpreted "sup" >>= (return . Superscript)
537+
558subscript = interpreted "sub" >>= (return . Subscript)
538subscript = interpreted "sub" >>= (return . Subscript)
559
539
560−whitespace = do
561− many1 spaceChar <?> "whitespace"
540+whitespace = many1 spaceChar >> return Space <?> "whitespace"
562− return Space
563
541
564−tabchar = do
565− tab
566− return (Str "\t")
542+tabchar = tab >> return (Str "\t")
567
543
568−str = do
569− notFollowedBy' oneWordReference
544+str = notFollowedBy' oneWordReference >>
570− result <- many1 (noneOf (specialChars ++ "\t\n "))
545+ many1 (noneOf (specialChars ++ "\t\n ")) >>= return . Str
571− return (Str result)
572
546
573-- an endline character that can be treated as a space, not a structural break
547-- an endline character that can be treated as a space, not a structural break
574−endline = try (do
548+endline = try $ do
575 newline
549 newline
576 notFollowedBy blankline
550 notFollowedBy blankline
577 -- parse potential list-starts at beginning of line differently in a list:
551 -- parse potential list-starts at beginning of line differently in a list:
578 st <- getState
552 st <- getState
579 if ((stateParserContext st) == ListItemState)
553 if ((stateParserContext st) == ListItemState)
580− then do notFollowedBy' anyOrderedListMarker
554+ then notFollowedBy' anyOrderedListMarker >> notFollowedBy' bulletListStart
581− notFollowedBy' bulletListStart
555+ else return ()
582− else option () pzero
583− return Space)
556+ return Space
584
557
585--
558--
586-- links
559-- links
@@ -628,10 +601,10 @@ referenceLink = try $ do
628uriScheme = oneOfStrings [ "http://", "https://", "ftp://", "file://",
601uriScheme = oneOfStrings [ "http://", "https://", "ftp://", "file://",
629 "mailto:", "news:", "telnet:" ]
602 "mailto:", "news:", "telnet:" ]
630
603
631−uri = try (do
604+uri = try $ do
632 scheme <- uriScheme
605 scheme <- uriScheme
633 identifier <- many1 (noneOf " \t\n")
606 identifier <- many1 (noneOf " \t\n")
634− return (scheme ++ identifier))
607+ return $ scheme ++ identifier
635
608
636autoURI = try $ do
609autoURI = try $ do
637 src <- uri
610 src <- uri
@@ -639,20 +612,20 @@ autoURI = try $ do
639
612
640emailChar = alphaNum <|> oneOf "-+_."
613emailChar = alphaNum <|> oneOf "-+_."
641
614
642−emailAddress = try (do
615+emailAddress = try $ do
643 firstLetter <- alphaNum
616 firstLetter <- alphaNum
644 restAddr <- many emailChar
617 restAddr <- many emailChar
645 let addr = firstLetter:restAddr
618 let addr = firstLetter:restAddr
646 char '@'
619 char '@'
647 dom <- domain
620 dom <- domain
648− return (addr ++ '@':dom))
621+ return $ addr ++ '@':dom
649
622
650domainChar = alphaNum <|> char '-'
623domainChar = alphaNum <|> char '-'
651
624
652−domain = try (do
625+domain = try $ do
653 first <- many1 domainChar
626 first <- many1 domainChar
654 dom <- many1 (try (do{ char '.'; many1 domainChar }))
627 dom <- many1 (try (do{ char '.'; many1 domainChar }))
655− return (joinWithSep "." (first:dom)))
628+ return $ joinWithSep "." (first:dom)
656
629
657autoEmail = try $ do
630autoEmail = try $ do
658 src <- emailAddress
631 src <- emailAddress
@@ -669,5 +642,5 @@ image = try $ do
669 src <- case lookupKeySrc keyTable ref of
642 src <- case lookupKeySrc keyTable ref of
670 Nothing -> fail "no corresponding key"
643 Nothing -> fail "no corresponding key"
671 Just target -> return target
644 Just target -> return target
672− return (Image (normalizeSpaces ref) src)
645+ return $ Image (normalizeSpaces ref) src
673
646
src/Text/Pandoc/Shared.hs+576−404
@@ -32,12 +32,11 @@ module Text.Pandoc.Shared (
32 splitBy,
32 splitBy,
33 splitByIndices,
33 splitByIndices,
34 substitute,
34 substitute,
35− -- * Text processing
36 joinWithSep,
35 joinWithSep,
36+ -- * Text processing
37 tabsToSpaces,
37 tabsToSpaces,
38 backslashEscapes,
38 backslashEscapes,
39 escapeStringUsing,
39 escapeStringUsing,
40− endsWith,
41 stripTrailingNewlines,
40 stripTrailingNewlines,
42 removeLeadingTrailingSpace,
41 removeLeadingTrailingSpace,
43 removeLeadingSpace,
42 removeLeadingSpace,
@@ -46,8 +45,33 @@ module Text.Pandoc.Shared (
46 camelCaseToHyphenated,
45 camelCaseToHyphenated,
47 toRomanNumeral,
46 toRomanNumeral,
48 -- * Parsing
47 -- * 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,
49 readWith,
71 readWith,
50 testStringWith,
72 testStringWith,
73+ ParserState (..),
74+ defaultParserState,
51 Reference (..),
75 Reference (..),
52 isNoteBlock,
76 isNoteBlock,
53 isKeyBlock,
77 isKeyBlock,
@@ -55,14 +79,10 @@ module Text.Pandoc.Shared (
55 HeaderType (..),
79 HeaderType (..),
56 ParserContext (..),
80 ParserContext (..),
57 QuoteContext (..),
81 QuoteContext (..),
58− ParserState (..),
59 NoteTable,
82 NoteTable,
60− defaultParserState,
83+ KeyTable,
61− nullBlock,
84+ lookupKeySrc,
62− failIfStrict,
85+ refsMatch,
63− escaped,
64− anyOrderedListMarker,
65− orderedListMarker,
66 -- * Native format prettyprinting
86 -- * Native format prettyprinting
67 prettyPandoc,
87 prettyPandoc,
68 -- * Pandoc block and inline list processing
88 -- * Pandoc block and inline list processing
@@ -74,214 +94,387 @@ module Text.Pandoc.Shared (
74 isHeaderBlock,
94 isHeaderBlock,
75 -- * Writer options
95 -- * Writer options
76 WriterOptions (..),
96 WriterOptions (..),
77− defaultWriterOptions,
97+ defaultWriterOptions
78− -- * Reference key lookup functions
79− KeyTable,
80− lookupKeySrc,
81− refsMatch,
82 ) where
98 ) where
99+
83import Text.Pandoc.Definition
100import Text.Pandoc.Definition
84import Text.ParserCombinators.Parsec
101import Text.ParserCombinators.Parsec
85−import Text.Pandoc.ParserCombinators
102+import Text.Pandoc.CharacterReferences ( characterReference )
86−import Text.Pandoc.Entities ( decodeEntities, escapeStringForXML )
87import Data.Char ( toLower, toUpper, ord, chr, isLower, isUpper )
103import Data.Char ( toLower, toUpper, ord, chr, isLower, isUpper )
88−import Data.List ( find, groupBy, isPrefixOf )
104+import Data.List ( find, groupBy, isPrefixOf, isSuffixOf )
89
105
90−-- | Parse a string with a given parser and state.
106+--
91−readWith :: GenParser Char ParserState a -- ^ parser
107+-- List processing
92− -> ParserState -- ^ initial state
108+--
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
99
109
100−-- | Parse a string with @parser@ (for testing).
110+-- | Split list by groups of one or more sep.
101−testStringWith :: (Show a) =>
111+splitBy :: (Eq a) => a -> [a] -> [[a]]
102− GenParser Char ParserState a
112+splitBy _ [] = []
103− -> String
113+splitBy sep lst =
104− -> IO ()
114+ let (first, rest) = break (== sep) lst
105−testStringWith parser str = putStrLn $ show $
115+ rest' = dropWhile (== sep) rest
106− readWith parser defaultParserState str
116+ in first:(splitBy sep rest')
107
117
108−data HeaderType
118+-- | Split list into chunks divided at specified indices.
109− = SingleHeader Char -- ^ Single line of characters underneath
119+splitByIndices :: [Int] -> [a] -> [[a]]
110− | DoubleHeader Char -- ^ Lines of characters above and below
120+splitByIndices [] lst = [lst]
111− deriving (Eq, Show)
121+splitByIndices (x:xs) lst =
122+ let (first, rest) = splitAt x lst in
123+ first:(splitByIndices (map (\y -> y - x) xs) rest)
112
124
113−data ParserContext
125+-- | Replace each occurrence of one sublist in a list with another.
114− = ListItemState -- ^ Used when running parser on list item contents
126+substitute :: (Eq a) => [a] -> [a] -> [a] -> [a]
115− | NullState -- ^ Default state
127+substitute _ _ [] = []
116− deriving (Eq, Show)
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)
117
133
118−data QuoteContext
134+-- | Joins a list of lists, separated by another list.
119− = InSingleQuote -- ^ Used when we're parsing inside single quotes
135+joinWithSep :: [a] -- ^ List to use as separator
120− | InDoubleQuote -- ^ Used when we're parsing inside double quotes
136+ -> [[a]] -- ^ Lists to join
121− | NoQuote -- ^ Used when we're not parsing inside quotes
137+ -> [a]
122− deriving (Eq, Show)
138+joinWithSep sep [] = []
139+joinWithSep sep lst = foldr1 (\a b -> a ++ sep ++ b) lst
123
140
124−type KeyTable = [([Inline], Target)]
141+--
142+-- Text processing
143+--
125
144
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)
127
151
128−-- | References from preliminary parsing
152+-- | Convert tabs to spaces in one line.
129−data Reference
153+tabsInLine :: Int -- ^ Number of spaces to next tab stop
130− = KeyBlock [Inline] Target -- ^ Key for reference-style link (label URL title)
154+ -> Int -- ^ Tabstop
131− | NoteBlock String [Block] -- ^ Footnote reference and contents
155+ -> String -- ^ Line to convert
132− | LineClump String -- ^ Raw clump of lines with blanks at end
156+ -> String
133− deriving (Eq, Read, Show)
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
134
165
135−-- | Auxiliary functions used in preliminary parsing
166+-- | Returns an association list of backslash escapes for the
136−isNoteBlock :: Reference -> Bool
167+-- designated characters.
137−isNoteBlock (NoteBlock _ _) = True
168+backslashEscapes :: [Char] -- ^ list of special characters to escape
138−isNoteBlock _ = False
169+ -> [(Char, String)]
170+backslashEscapes = map (\ch -> (ch, ['\\',ch]))
139
171
140−isKeyBlock :: Reference -> Bool
172+-- | Escape a string of characters, using an association list of
141−isKeyBlock (KeyBlock _ _) = True
173+-- characters and strings.
142−isKeyBlock _ = False
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
143
181
144−isLineClump :: Reference -> Bool
182+-- | Strip trailing newlines from string.
145−isLineClump (LineClump _) = True
183+stripTrailingNewlines :: String -> String
146−isLineClump _ = False
184+stripTrailingNewlines = reverse . dropWhile (== '\n') . reverse
147
185
148−data ParserState = ParserState
186+-- | Remove leading and trailing space (including newlines) from string.
149− { stateParseRaw :: Bool, -- ^ Parse untranslatable HTML
187+removeLeadingTrailingSpace :: String -> String
150− -- and LaTeX?
188+removeLeadingTrailingSpace = removeLeadingSpace . removeTrailingSpace
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
169
189
170−defaultParserState :: ParserState
190+-- | Remove leading space (including newlines) from string.
171−defaultParserState =
191+removeLeadingSpace :: String -> String
172− ParserState { stateParseRaw = False,
192+removeLeadingSpace = dropWhile (`elem` " \n\t")
173− stateParserContext = NullState,
193+
174− stateQuoteContext = NoQuote,
194+-- | Remove trailing space (including newlines) from string.
175− stateKeys = [],
195+removeTrailingSpace :: String -> String
176− stateNotes = [],
196+removeTrailingSpace = reverse . removeLeadingSpace . reverse
177− stateTabStop = 4,
197+
178− stateStandalone = False,
198+-- | Strip leading and trailing characters from string
179− stateTitle = [],
199+stripFirstAndLast :: String -> String
180− stateAuthors = [],
200+stripFirstAndLast str =
181− stateDate = [],
201+ drop 1 $ take ((length str) - 1) str
182− stateStrict = False,
202+
183− stateSmart = False,
203+-- | Change CamelCase word to hyphenated lowercase (e.g., camel-case).
184− stateColumns = 80,
204+camelCaseToHyphenated :: String -> String
185− stateHeaderTable = [] }
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)
186
383
187-- | Parses a character and returns 'Null' (so that the parser can move on
384-- | Parses a character and returns 'Null' (so that the parser can move on
188-- if it gets stuck).
385-- if it gets stuck).
189nullBlock :: GenParser Char st Block
386nullBlock :: GenParser Char st Block
190−nullBlock = do
191− anyChar
192− return Null
387+nullBlock = anyChar >> return Null
193
388
194-- | Fail if reader is in strict markdown syntax mode.
389-- | Fail if reader is in strict markdown syntax mode.
195failIfStrict :: GenParser Char ParserState ()
390failIfStrict :: GenParser Char ParserState ()
196failIfStrict = do
391failIfStrict = do
197 state <- getState
392 state <- getState
198− if stateStrict state then fail "Strict mode" else return ()
393+ if stateStrict state then fail "strict mode" else return ()
199
394
200-- | Parses backslash, then applies character parser.
395-- | Parses backslash, then applies character parser.
201escaped :: GenParser Char st Char -- ^ Parser for character to escape
396escaped :: GenParser Char st Char -- ^ Parser for character to escape
202 -> GenParser Char st Inline
397 -> GenParser Char st Inline
203−escaped parser = try (do
398+escaped parser = try $ do
204− char '\\'
399+ char '\\'
205− result <- parser
400+ result <- parser
206− return (Str [result]))
401+ return (Str [result])
207
402
208-- | Parses an uppercase roman numeral and returns (UpperRoman, number).
403-- | Parses an uppercase roman numeral and returns (UpperRoman, number).
209upperRoman :: GenParser Char st (ListNumberStyle, Int)
404upperRoman :: GenParser Char st (ListNumberStyle, Int)
210upperRoman = do
405upperRoman = do
211− num <- romanNumeral True
406+ num <- romanNumeral True
212− return (UpperRoman, num)
407+ return (UpperRoman, num)
213
408
214-- | Parses a lowercase roman numeral and returns (LowerRoman, number).
409-- | Parses a lowercase roman numeral and returns (LowerRoman, number).
215lowerRoman :: GenParser Char st (ListNumberStyle, Int)
410lowerRoman :: GenParser Char st (ListNumberStyle, Int)
216lowerRoman = do
411lowerRoman = do
217− num <- romanNumeral False
412+ num <- romanNumeral False
218− return (LowerRoman, num)
413+ return (LowerRoman, num)
219
414
220-- | Parses a decimal numeral and returns (Decimal, number).
415-- | Parses a decimal numeral and returns (Decimal, number).
221decimal :: GenParser Char st (ListNumberStyle, Int)
416decimal :: GenParser Char st (ListNumberStyle, Int)
222decimal = do
417decimal = do
223− num <- many1 digit
418+ num <- many1 digit
224− return (Decimal, read num)
419+ return (Decimal, read num)
225
420
226-- | Parses a '#' returns (DefaultStyle, 1).
421-- | Parses a '#' returns (DefaultStyle, 1).
227defaultNum :: GenParser Char st (ListNumberStyle, Int)
422defaultNum :: GenParser Char st (ListNumberStyle, Int)
228defaultNum = do
423defaultNum = do
229− char '#'
424+ char '#'
230− return (DefaultStyle, 1)
425+ return (DefaultStyle, 1)
231
426
232-- | Parses a lowercase letter and returns (LowerAlpha, number).
427-- | Parses a lowercase letter and returns (LowerAlpha, number).
233lowerAlpha :: GenParser Char st (ListNumberStyle, Int)
428lowerAlpha :: GenParser Char st (ListNumberStyle, Int)
234lowerAlpha = do
429lowerAlpha = do
235− ch <- oneOf ['a'..'z']
430+ ch <- oneOf ['a'..'z']
236− return (LowerAlpha, ord ch - ord 'a' + 1)
431+ return (LowerAlpha, ord ch - ord 'a' + 1)
237
432
238-- | Parses an uppercase letter and returns (UpperAlpha, number).
433-- | Parses an uppercase letter and returns (UpperAlpha, number).
239upperAlpha :: GenParser Char st (ListNumberStyle, Int)
434upperAlpha :: GenParser Char st (ListNumberStyle, Int)
240upperAlpha = do
435upperAlpha = do
241− ch <- oneOf ['A'..'Z']
436+ ch <- oneOf ['A'..'Z']
242− return (UpperAlpha, ord ch - ord 'A' + 1)
437+ return (UpperAlpha, ord ch - ord 'A' + 1)
243
438
244-- | Parses a roman numeral i or I
439-- | Parses a roman numeral i or I
245romanOne :: GenParser Char st (ListNumberStyle, Int)
440romanOne :: GenParser Char st (ListNumberStyle, Int)
246−romanOne = (do char 'i'
247− return (LowerRoman, 1)) <|>
441+romanOne = (char 'i' >> return (LowerRoman, 1)) <|>
248− (do char 'I'
249− return (UpperRoman, 1))
442+ (char 'I' >> return (UpperRoman, 1))
250
443
251-- | Parses an ordered list marker and returns list attributes.
444-- | Parses an ordered list marker and returns list attributes.
252anyOrderedListMarker :: GenParser Char st ListAttributes
445anyOrderedListMarker :: GenParser Char st ListAttributes
253−anyOrderedListMarker = choice $ [delimParser numParser | delimParser <-
446+anyOrderedListMarker = choice $
254− [inPeriod, inOneParen, inTwoParens],
447+ [delimParser numParser | delimParser <- [inPeriod, inOneParen, inTwoParens],
255− numParser <- [decimal, defaultNum, romanOne,
448+ numParser <- [decimal, defaultNum, romanOne,
256− lowerAlpha, lowerRoman, upperAlpha, upperRoman]]
449+ lowerAlpha, lowerRoman, upperAlpha, upperRoman]]
257
450
258-- | Parses a list number (num) followed by a period, returns list attributes.
451-- | Parses a list number (num) followed by a period, returns list attributes.
259inPeriod :: GenParser Char st (ListNumberStyle, Int)
452inPeriod :: GenParser Char st (ListNumberStyle, Int)
260 -> GenParser Char st ListAttributes
453 -> GenParser Char st ListAttributes
261inPeriod num = try $ do
454inPeriod num = try $ do
262− (style, start) <- num
455+ (style, start) <- num
263− char '.'
456+ char '.'
264− let delim = if style == DefaultStyle
457+ let delim = if style == DefaultStyle
265− then DefaultDelim
458+ then DefaultDelim
266− else Period
459+ else Period
267− return (start, style, delim)
460+ return (start, style, delim)
268
461
269-- | Parses a list number (num) followed by a paren, returns list attributes.
462-- | Parses a list number (num) followed by a paren, returns list attributes.
270inOneParen :: GenParser Char st (ListNumberStyle, Int)
463inOneParen :: GenParser Char st (ListNumberStyle, Int)
271 -> GenParser Char st ListAttributes
464 -> GenParser Char st ListAttributes
272inOneParen num = try $ do
465inOneParen num = try $ do
273− (style, start) <- num
466+ (style, start) <- num
274− char ')'
467+ char ')'
275− return (start, style, OneParen)
468+ return (start, style, OneParen)
276
469
277-- | Parses a list number (num) enclosed in parens, returns list attributes.
470-- | Parses a list number (num) enclosed in parens, returns list attributes.
278inTwoParens :: GenParser Char st (ListNumberStyle, Int)
471inTwoParens :: GenParser Char st (ListNumberStyle, Int)
279 -> GenParser Char st ListAttributes
472 -> GenParser Char st ListAttributes
280inTwoParens num = try $ do
473inTwoParens num = try $ do
281− char '('
474+ char '('
282− (style, start) <- num
475+ (style, start) <- num
283− char ')'
476+ char ')'
284− return (start, style, TwoParens)
477+ return (start, style, TwoParens)
285
478
286-- | Parses an ordered list marker with a given style and delimiter,
479-- | Parses an ordered list marker with a given style and delimiter,
287-- returns number.
480-- returns number.
@@ -289,21 +482,158 @@ orderedListMarker :: ListNumberStyle
289 -> ListNumberDelim
482 -> ListNumberDelim
290 -> GenParser Char st Int
483 -> GenParser Char st Int
291orderedListMarker style delim = do
484orderedListMarker style delim = do
292− let num = case style of
485+ let num = case style of
293− DefaultStyle -> decimal <|> defaultNum
486+ DefaultStyle -> decimal <|> defaultNum
294− Decimal -> decimal
487+ Decimal -> decimal
295− UpperRoman -> upperRoman
488+ UpperRoman -> upperRoman
296− LowerRoman -> lowerRoman
489+ LowerRoman -> lowerRoman
297− UpperAlpha -> upperAlpha
490+ UpperAlpha -> upperAlpha
298− LowerAlpha -> lowerAlpha
491+ LowerAlpha -> lowerAlpha
299− let context = case delim of
492+ let context = case delim of
300− DefaultDelim -> inPeriod
493+ DefaultDelim -> inPeriod
301− Period -> inPeriod
494+ Period -> inPeriod
302− OneParen -> inOneParen
495+ OneParen -> inOneParen
303− TwoParens -> inTwoParens
496+ TwoParens -> inTwoParens
304− (start, style, delim) <- context num
497+ (start, style, delim) <- context num
305− return start
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
306
632
633+--
634+-- Native format prettyprinting
635+--
636+
307-- | Indent string as a block.
637-- | Indent string as a block.
308indentBy :: Int -- ^ Number of spaces to indent the block
638indentBy :: Int -- ^ Number of spaces to indent the block
309 -> Int -- ^ Number of spaces (rel to block) to indent first line
639 -> Int -- ^ Number of spaces (rel to block) to indent first line
@@ -311,9 +641,10 @@ indentBy :: Int -- ^ Number of spaces to indent the block
311 -> String
641 -> String
312indentBy num first [] = ""
642indentBy num first [] = ""
313indentBy num first str =
643indentBy num first str =
314− let (firstLine:restLines) = lines str
644+ let (firstLine:restLines) = lines str
315− firstLineIndent = num + first in
645+ firstLineIndent = num + first
316− (replicate firstLineIndent ' ') ++ firstLine ++ "\n" ++ (joinWithSep "\n" $ map (\line -> (replicate num ' ') ++ line) restLines)
646+ in (replicate firstLineIndent ' ') ++ firstLine ++ "\n" ++
647+ (joinWithSep "\n" $ map ((replicate num ' ') ++ ) restLines)
317
648
318-- | Prettyprint list of Pandoc blocks elements.
649-- | Prettyprint list of Pandoc blocks elements.
319prettyBlockList :: Int -- ^ Number of spaces to indent list of blocks
650prettyBlockList :: Int -- ^ Number of spaces to indent list of blocks
@@ -321,142 +652,40 @@ prettyBlockList :: Int -- ^ Number of spaces to indent list of blocks
321 -> String
652 -> String
322prettyBlockList indent [] = indentBy indent 0 "[]"
653prettyBlockList indent [] = indentBy indent 0 "[]"
323prettyBlockList indent blocks = indentBy indent (-2) $ "[ " ++
654prettyBlockList indent blocks = indentBy indent (-2) $ "[ " ++
324− (joinWithSep "\n, " (map prettyBlock blocks)) ++ " ]"
655+ (joinWithSep "\n, " (map prettyBlock blocks)) ++ " ]"
325
656
326-- | Prettyprint Pandoc block element.
657-- | Prettyprint Pandoc block element.
327prettyBlock :: Block -> String
658prettyBlock :: Block -> String
328prettyBlock (BlockQuote blocks) = "BlockQuote\n " ++
659prettyBlock (BlockQuote blocks) = "BlockQuote\n " ++
329 (prettyBlockList 2 blocks)
660 (prettyBlockList 2 blocks)
330prettyBlock (OrderedList attribs blockLists) =
661prettyBlock (OrderedList attribs blockLists) =
331− "OrderedList " ++ show attribs ++ "\n" ++ indentBy 2 0 ("[ " ++
662+ "OrderedList " ++ show attribs ++ "\n" ++ indentBy 2 0 ("[ " ++
332− (joinWithSep ", " $ map (\blocks -> prettyBlockList 2 blocks)
663+ (joinWithSep ", " $ map (\blocks -> prettyBlockList 2 blocks)
333− blockLists)) ++ " ]"
664+ blockLists)) ++ " ]"
334prettyBlock (BulletList blockLists) = "BulletList\n" ++
665prettyBlock (BulletList blockLists) = "BulletList\n" ++
335− indentBy 2 0 ("[ " ++ (joinWithSep ", "
666+ indentBy 2 0 ("[ " ++ (joinWithSep ", "
336− (map (\blocks -> prettyBlockList 2 blocks) blockLists))) ++ " ]"
667+ (map (\blocks -> prettyBlockList 2 blocks) blockLists))) ++ " ]"
337prettyBlock (DefinitionList blockLists) = "DefinitionList\n" ++
668prettyBlock (DefinitionList blockLists) = "DefinitionList\n" ++
338− indentBy 2 0 ("[" ++ (joinWithSep ",\n"
669+ indentBy 2 0 ("[" ++ (joinWithSep ",\n"
339− (map (\(term, blocks) -> " (" ++ show term ++ ",\n" ++
670+ (map (\(term, blocks) -> " (" ++ show term ++ ",\n" ++
340− indentBy 1 2 (prettyBlockList 2 blocks) ++ " )") blockLists))) ++ " ]"
671+ indentBy 1 2 (prettyBlockList 2 blocks) ++ " )") blockLists))) ++ " ]"
341prettyBlock (Table caption aligns widths header rows) =
672prettyBlock (Table caption aligns widths header rows) =
342− "Table " ++ show caption ++ " " ++ show aligns ++ " " ++
673+ "Table " ++ show caption ++ " " ++ show aligns ++ " " ++
343− show widths ++ "\n" ++ prettyRow header ++ " [\n" ++
674+ show widths ++ "\n" ++ prettyRow header ++ " [\n" ++
344− (joinWithSep ",\n" (map prettyRow rows)) ++ " ]"
675+ (joinWithSep ",\n" (map prettyRow rows)) ++ " ]"
345− where prettyRow cols = indentBy 2 0 ("[ " ++ (joinWithSep ", "
676+ where prettyRow cols = indentBy 2 0 ("[ " ++ (joinWithSep ", "
346− (map (\blocks -> prettyBlockList 2 blocks)
677+ (map (\blocks -> prettyBlockList 2 blocks)
347− cols))) ++ " ]"
678+ cols))) ++ " ]"
348prettyBlock block = show block
679prettyBlock block = show block
349
680
350-- | Prettyprint Pandoc document.
681-- | Prettyprint Pandoc document.
351prettyPandoc :: Pandoc -> String
682prettyPandoc :: Pandoc -> String
352−prettyPandoc (Pandoc meta blocks) = "Pandoc " ++ "(" ++ (show meta) ++
683+prettyPandoc (Pandoc meta blocks) = "Pandoc " ++ "(" ++ show meta ++
353− ")\n" ++ (prettyBlockList 0 blocks) ++ "\n"
684+ ")\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))
361
685
362−-- | Convert tabs to spaces in one line.
686+--
363−tabsInLine :: Int -- ^ Number of spaces to next tab stop
687+-- Pandoc block and inline list processing
364− -> Int -- ^ Tabstop
688+--
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)
460
689
461-- | Generate infinite lazy list of markers for an ordered list,
690-- | Generate infinite lazy list of markers for an ordered list,
462-- depending on list attributes.
691-- depending on list attributes.
@@ -466,8 +695,10 @@ orderedListMarkers (start, numstyle, numdelim) =
466 seq = case numstyle of
695 seq = case numstyle of
467 DefaultStyle -> map show [start..]
696 DefaultStyle -> map show [start..]
468 Decimal -> map show [start..]
697 Decimal -> map show [start..]
469− UpperAlpha -> drop (start - 1) $ cycle $ map singleton ['A'..'Z']
698+ UpperAlpha -> drop (start - 1) $ cycle $
470− LowerAlpha -> drop (start - 1) $ cycle $ map singleton ['a'..'z']
699+ map singleton ['A'..'Z']
700+ LowerAlpha -> drop (start - 1) $ cycle $
701+ map singleton ['a'..'z']
471 UpperRoman -> map toRomanNumeral [start..]
702 UpperRoman -> map toRomanNumeral [start..]
472 LowerRoman -> map (map toLower . toRomanNumeral) [start..]
703 LowerRoman -> map (map toLower . toRomanNumeral) [start..]
473 inDelim str = case numdelim of
704 inDelim str = case numdelim of
@@ -477,27 +708,6 @@ orderedListMarkers (start, numstyle, numdelim) =
477 TwoParens -> "(" ++ str ++ ")"
708 TwoParens -> "(" ++ str ++ ")"
478 in map inDelim seq
709 in map inDelim seq
479
710
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−
501-- | Normalize a list of inline elements: remove leading and trailing
711-- | Normalize a list of inline elements: remove leading and trailing
502-- @Space@ elements, collapse double @Space@s into singles, and
712-- @Space@ elements, collapse double @Space@s into singles, and
503-- remove empty Str elements.
713-- remove empty Str elements.
@@ -507,16 +717,14 @@ normalizeSpaces list =
507 let removeDoubles [] = []
717 let removeDoubles [] = []
508 removeDoubles (Space:Space:rest) = removeDoubles (Space:rest)
718 removeDoubles (Space:Space:rest) = removeDoubles (Space:rest)
509 removeDoubles ((Str ""):rest) = removeDoubles rest
719 removeDoubles ((Str ""):rest) = removeDoubles rest
510− removeDoubles (x:rest) = x:(removeDoubles rest) in
720+ removeDoubles (x:rest) = x:(removeDoubles rest)
511− let removeLeading [] = []
721+ removeLeading (Space:xs) = removeLeading xs
512− removeLeading lst = if ((head lst) == Space)
722+ removeLeading x = x
513− then tail lst
723+ removeTrailing [] = []
514− else lst in
724+ removeTrailing lst = if (last lst == Space)
515− let removeTrailing [] = []
725+ then init lst
516− removeTrailing lst = if ((last lst) == Space)
726+ else lst
517− then init lst
727+ in removeLeading $ removeTrailing $ removeDoubles list
518− else lst in
519− removeLeading $ removeTrailing $ removeDoubles list
520
728
521-- | Change final list item from @Para@ to @Plain@ if the list should
729-- | Change final list item from @Para@ to @Plain@ if the list should
522-- be compact.
730-- be compact.
@@ -524,122 +732,86 @@ compactify :: [[Block]] -- ^ List of list items (each a list of blocks)
524 -> [[Block]]
732 -> [[Block]]
525compactify [] = []
733compactify [] = []
526compactify items =
734compactify items =
527− let final = last items
735+ let final = last items
528− others = init items in
736+ others = init items
529− case final of
737+ in case final of
530− [Para a] -> if any containsPara others
738+ [Para a] -> if any containsPara others
531− then items
739+ then items
532− else others ++ [[Plain a]]
740+ else others ++ [[Plain a]]
533− otherwise -> items
741+ otherwise -> items
534
742
535containsPara :: [Block] -> Bool
743containsPara :: [Block] -> Bool
536containsPara [] = False
744containsPara [] = False
537containsPara ((Para a):rest) = True
745containsPara ((Para a):rest) = True
538−containsPara ((BulletList items):rest) = (any containsPara items) ||
746+containsPara ((BulletList items):rest) = any containsPara items ||
539− (containsPara rest)
747+ containsPara rest
540−containsPara ((OrderedList _ items):rest) = (any containsPara items) ||
748+containsPara ((OrderedList _ items):rest) = any containsPara items ||
541− (containsPara rest)
749+ containsPara rest
750+containsPara ((DefinitionList items):rest) = any containsPara (map snd items) ||
751+ containsPara rest
542containsPara (x:rest) = containsPara rest
752containsPara (x:rest) = containsPara rest
543
753
544-- | Data structure for defining hierarchical Pandoc documents
754-- | Data structure for defining hierarchical Pandoc documents
545data Element = Blk Block
755data Element = Blk Block
546 | Sec [Inline] [Element] deriving (Eq, Read, Show)
756 | Sec [Inline] [Element] deriving (Eq, Read, Show)
547
757
548−-- | Returns true on Header block with level at least 'level'
758+-- | Returns @True@ on Header block with at least the specified level
549headerAtLeast :: Int -> Block -> Bool
759headerAtLeast :: Int -> Block -> Bool
550headerAtLeast level (Header x _) = x <= level
760headerAtLeast level (Header x _) = x <= level
551headerAtLeast level _ = False
761headerAtLeast level _ = False
552
762
553−-- | Convert list of Pandoc blocks into list of Elements (hierarchical)
763+-- | Convert list of Pandoc blocks into (hierarchical) list of Elements
554hierarchicalize :: [Block] -> [Element]
764hierarchicalize :: [Block] -> [Element]
555hierarchicalize [] = []
765hierarchicalize [] = []
556hierarchicalize (block:rest) =
766hierarchicalize (block:rest) =
557 case block of
767 case block of
558− (Header level title) -> let (thisSection, rest') = break (headerAtLeast
768+ (Header level title) ->
559− level) rest in
769+ let (thisSection, rest') = break (headerAtLeast level) rest
560− (Sec title (hierarchicalize thisSection)):
770+ in (Sec title (hierarchicalize thisSection)):(hierarchicalize rest')
561− (hierarchicalize rest')
771+ x -> (Blk x):(hierarchicalize rest)
562− x -> (Blk x):(hierarchicalize rest)
563
772
564-- | True if block is a Header block.
773-- | True if block is a Header block.
565isHeaderBlock :: Block -> Bool
774isHeaderBlock :: Block -> Bool
566isHeaderBlock (Header _ _) = True
775isHeaderBlock (Header _ _) = True
567isHeaderBlock _ = False
776isHeaderBlock _ = False
568
777
778+--
779+-- Writer options
780+--
781+
569-- | Options for writers
782-- | Options for writers
570data WriterOptions = WriterOptions
783data WriterOptions = WriterOptions
571− { writerStandalone :: Bool -- ^ Include header and footer
784+ { writerStandalone :: Bool -- ^ Include header and footer
572− , writerTitlePrefix :: String -- ^ Prefix for HTML titles
785+ , writerHeader :: String -- ^ Header for the document
573− , writerHeader :: String -- ^ Header for the document
786+ , writerTitlePrefix :: String -- ^ Prefix for HTML titles
574− , writerIncludeBefore :: String -- ^ String to include before the body
787+ , writerTabStop :: Int -- ^ Tabstop for conversion btw spaces and tabs
575− , writerIncludeAfter :: String -- ^ String to include after the body
788+ , writerTableOfContents :: Bool -- ^ Include table of contents
576− , writerTableOfContents :: Bool -- ^ Include table of contents
789+ , writerS5 :: Bool -- ^ We're writing S5
577− , writerS5 :: Bool -- ^ We're writing S5
790+ , writerUseASCIIMathML :: Bool -- ^ Use ASCIIMathML
578− , writerUseASCIIMathML :: Bool -- ^ Use ASCIIMathML
791+ , writerASCIIMathMLURL :: Maybe String -- ^ URL to asciiMathML.js
579− , writerASCIIMathMLURL :: Maybe String -- ^ URL to asciiMathML.js
792+ , writerIgnoreNotes :: Bool -- ^ Ignore footnotes (used in making toc)
580− , writerIgnoreNotes :: Bool -- ^ Ignore footnotes (used in making toc)
793+ , writerIncremental :: Bool -- ^ Incremental S5 lists
581− , writerIncremental :: Bool -- ^ Incremental S5 lists
794+ , writerNumberSections :: Bool -- ^ Number sections in LaTeX
582− , writerNumberSections :: Bool -- ^ Number sections in LaTeX
795+ , writerIncludeBefore :: String -- ^ String to include before the body
583− , writerStrictMarkdown :: Bool -- ^ Use strict markdown syntax
796+ , writerIncludeAfter :: String -- ^ String to include after the body
584− , writerReferenceLinks :: Bool -- ^ Use reference links in writing markdown, rst
797+ , writerStrictMarkdown :: Bool -- ^ Use strict markdown syntax
585− , writerTabStop :: Int -- ^ Tabstop for conversion between
798+ , writerReferenceLinks :: Bool -- ^ Use reference links in writing markdown, rst
586− -- spaces and tabs
799+ } deriving Show
587− } deriving Show
588
800
589-- | Default writer options.
801-- | Default writer options.
590defaultWriterOptions =
802defaultWriterOptions =
591− WriterOptions { writerStandalone = False,
803+ WriterOptions { writerStandalone = False,
592− writerHeader = "",
804+ writerHeader = "",
593− writerTitlePrefix = "",
805+ writerTitlePrefix = "",
594− writerTabStop = 4,
806+ writerTabStop = 4,
595− writerTableOfContents = False,
807+ writerTableOfContents = False,
596− writerS5 = False,
808+ writerS5 = False,
597− writerUseASCIIMathML = False,
809+ writerUseASCIIMathML = False,
598− writerASCIIMathMLURL = Nothing,
810+ writerASCIIMathMLURL = Nothing,
599− writerIgnoreNotes = False,
811+ writerIgnoreNotes = False,
600− writerIncremental = False,
812+ writerIncremental = False,
601− writerNumberSections = False,
813+ writerNumberSections = False,
602− writerIncludeBefore = "",
814+ writerIncludeBefore = "",
603− writerIncludeAfter = "",
815+ writerIncludeAfter = "",
604− writerStrictMarkdown = False,
816+ writerStrictMarkdown = False,
605− writerReferenceLinks = False }
817+ 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
src/Text/Pandoc/Writers/ConTeXt.hs+10−13
@@ -18,7 +18,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
18
19{- |
19{- |
20 Module : Text.Pandoc.Writers.ConTeXt
20 Module : Text.Pandoc.Writers.ConTeXt
21− Copyright : Copyright (C) 2006-7 John MacFarlane
21+ Copyright : Copyright (C) 2007 John MacFarlane
22 License : GNU GPL, version 2 or above
22 License : GNU GPL, version 2 or above
23
23
24 Maintainer : John MacFarlane <jgm@berkeley.edu>
24 Maintainer : John MacFarlane <jgm@berkeley.edu>
@@ -27,9 +27,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27
27
28Conversion of 'Pandoc' format into ConTeXt.
28Conversion of 'Pandoc' format into ConTeXt.
29-}
29-}
30−module Text.Pandoc.Writers.ConTeXt (
30+module Text.Pandoc.Writers.ConTeXt ( writeConTeXt ) where
31− writeConTeXt
32− ) where
33import Text.Pandoc.Definition
31import Text.Pandoc.Definition
34import Text.Pandoc.Shared
32import Text.Pandoc.Shared
35import Text.Printf ( printf )
33import Text.Printf ( printf )
@@ -40,8 +38,7 @@ type WriterState = Int -- number of next URL reference
40
38
41-- | Convert Pandoc to ConTeXt.
39-- | Convert Pandoc to ConTeXt.
42writeConTeXt :: WriterOptions -> Pandoc -> String
40writeConTeXt :: WriterOptions -> Pandoc -> String
43−writeConTeXt options document =
41+writeConTeXt options document = evalState (pandocToConTeXt options document) 1
44− evalState (pandocToConTeXt options document) 1
45
42
46pandocToConTeXt :: WriterOptions -> Pandoc -> State WriterState String
43pandocToConTeXt :: WriterOptions -> Pandoc -> State WriterState String
47pandocToConTeXt options (Pandoc meta blocks) = do
44pandocToConTeXt options (Pandoc meta blocks) = do
@@ -111,8 +108,8 @@ stringToConTeXt = concatMap escapeCharForConTeXt
111-- | Convert Pandoc block element to ConTeXt.
108-- | Convert Pandoc block element to ConTeXt.
112blockToConTeXt :: Block -> State WriterState String
109blockToConTeXt :: Block -> State WriterState String
113blockToConTeXt Null = return ""
110blockToConTeXt Null = return ""
114−blockToConTeXt (Plain lst) = inlineListToConTeXt lst >>= (return . (++ "\n"))
111+blockToConTeXt (Plain lst) = inlineListToConTeXt lst >>= return . (++ "\n")
115−blockToConTeXt (Para lst) = inlineListToConTeXt lst >>= (return . (++ "\n\n"))
112+blockToConTeXt (Para lst) = inlineListToConTeXt lst >>= return . (++ "\n\n")
116blockToConTeXt (BlockQuote lst) = do
113blockToConTeXt (BlockQuote lst) = do
117 contents <- blockListToConTeXt lst
114 contents <- blockListToConTeXt lst
118 return $ "\\startblockquote\n" ++ contents ++ "\\stopblockquote\n\n"
115 return $ "\\startblockquote\n" ++ contents ++ "\\stopblockquote\n\n"
@@ -137,12 +134,12 @@ blockToConTeXt (OrderedList attribs lst) = case attribs of
137 return $ "\\startitemize" ++ markerWidth' ++ "\n" ++ concat contents ++
134 return $ "\\startitemize" ++ markerWidth' ++ "\n" ++ concat contents ++
138 "\\stopitemize\n"
135 "\\stopitemize\n"
139blockToConTeXt (DefinitionList lst) =
136blockToConTeXt (DefinitionList lst) =
140− mapM defListItemToConTeXt lst >>= (return . (++ "\n") . concat)
137+ mapM defListItemToConTeXt lst >>= return . (++ "\n") . concat
141blockToConTeXt HorizontalRule = return "\\thinrule\n\n"
138blockToConTeXt HorizontalRule = return "\\thinrule\n\n"
142blockToConTeXt (Header level lst) = do
139blockToConTeXt (Header level lst) = do
143 contents <- inlineListToConTeXt lst
140 contents <- inlineListToConTeXt lst
144− return $ if (level > 0) && (level <= 3)
141+ return $ if level > 0 && level <= 3
145− then "\\" ++ (concat (replicate (level - 1) "sub")) ++
142+ then "\\" ++ concat (replicate (level - 1) "sub") ++
146 "section{" ++ contents ++ "}\n\n"
143 "section{" ++ contents ++ "}\n\n"
147 else contents ++ "\n\n"
144 else contents ++ "\n\n"
148blockToConTeXt (Table caption aligns widths heads rows) = do
145blockToConTeXt (Table caption aligns widths heads rows) = do
@@ -186,12 +183,12 @@ defListItemToConTeXt (term, def) = do
186
183
187-- | Convert list of block elements to ConTeXt.
184-- | Convert list of block elements to ConTeXt.
188blockListToConTeXt :: [Block] -> State WriterState String
185blockListToConTeXt :: [Block] -> State WriterState String
189−blockListToConTeXt lst = mapM blockToConTeXt lst >>= (return . concat)
186+blockListToConTeXt lst = mapM blockToConTeXt lst >>= return . concat
190
187
191-- | Convert list of inline elements to ConTeXt.
188-- | Convert list of inline elements to ConTeXt.
192inlineListToConTeXt :: [Inline] -- ^ Inlines to convert
189inlineListToConTeXt :: [Inline] -- ^ Inlines to convert
193 -> State WriterState String
190 -> State WriterState String
194−inlineListToConTeXt lst = mapM inlineToConTeXt lst >>= (return . concat)
191+inlineListToConTeXt lst = mapM inlineToConTeXt lst >>= return . concat
195
192
196isQuoted :: Inline -> Bool
193isQuoted :: Inline -> Bool
197isQuoted (Quoted _ _) = True
194isQuoted (Quoted _ _) = True
src/Text/Pandoc/Writers/Docbook.hs+104−95
@@ -30,16 +30,35 @@ Conversion of 'Pandoc' documents to Docbook XML.
30module Text.Pandoc.Writers.Docbook ( writeDocbook) where
30module Text.Pandoc.Writers.Docbook ( writeDocbook) where
31import Text.Pandoc.Definition
31import Text.Pandoc.Definition
32import Text.Pandoc.Shared
32import Text.Pandoc.Shared
33−import Text.Pandoc.Entities ( escapeStringForXML )
33+import Data.List ( isPrefixOf, drop )
34−import Data.Char ( toLower, ord )
35−import Data.List ( isPrefixOf, partition, drop )
36import Text.PrettyPrint.HughesPJ hiding ( Str )
34import Text.PrettyPrint.HughesPJ hiding ( Str )
37
35
38−
39--
36--
40-- code to format XML
37-- code to format XML
41--
38--
42
39
40+-- | Escape one character as needed for XML.
41+escapeCharForXML :: Char -> String
42+escapeCharForXML x = case x of
43+ '&' -> "&amp;"
44+ '<' -> "&lt;"
45+ '>' -> "&gt;"
46+ '"' -> "&quot;"
47+ '\160' -> "&nbsp;"
48+ c -> [c]
49+
50+-- | True if the character needs to be escaped.
51+needsEscaping :: Char -> Bool
52+needsEscaping c = c `elem` "&<>\"\160"
53+
54+-- | Escape string as needed for XML. Entity references are not preserved.
55+escapeStringForXML :: String -> String
56+escapeStringForXML "" = ""
57+escapeStringForXML str =
58+ case break needsEscaping str of
59+ (okay, "") -> okay
60+ (okay, (c:cs)) -> okay ++ escapeCharForXML c ++ escapeStringForXML cs
61+
43-- | Return a text object with a string of formatted XML attributes.
62-- | Return a text object with a string of formatted XML attributes.
44attributeList :: [(String, String)] -> Doc
63attributeList :: [(String, String)] -> Doc
45attributeList = text . concatMap
64attributeList = text . concatMap
@@ -52,10 +71,10 @@ inTags:: Bool -> String -> [(String, String)] -> Doc -> Doc
52inTags isIndented tagType attribs contents =
71inTags isIndented tagType attribs contents =
53 let openTag = char '<' <> text tagType <> attributeList attribs <>
72 let openTag = char '<' <> text tagType <> attributeList attribs <>
54 char '>'
73 char '>'
55− closeTag = text "</" <> text tagType <> char '>' in
74+ closeTag = text "</" <> text tagType <> char '>'
56− if isIndented
75+ in if isIndented
57− then openTag $$ nest 2 contents $$ closeTag
76+ then openTag $$ nest 2 contents $$ closeTag
58− else openTag <> contents <> closeTag
77+ else openTag <> contents <> closeTag
59
78
60-- | Return a self-closing tag of tagType with specified attributes
79-- | Return a self-closing tag of tagType with specified attributes
61selfClosingTag :: String -> [(String, String)] -> Doc
80selfClosingTag :: String -> [(String, String)] -> Doc
@@ -79,42 +98,42 @@ authorToDocbook :: [Char] -> Doc
79authorToDocbook name = inTagsIndented "author" $
98authorToDocbook name = inTagsIndented "author" $
80 if ',' `elem` name
99 if ',' `elem` name
81 then -- last name first
100 then -- last name first
82− let (lastname, rest) = break (==',') name
101+ let (lastname, rest) = break (==',') name
83− firstname = removeLeadingSpace rest in
102+ firstname = removeLeadingSpace rest in
84− inTagsSimple "firstname" (text $ escapeStringForXML firstname) <>
103+ inTagsSimple "firstname" (text $ escapeStringForXML firstname) <>
85− inTagsSimple "surname" (text $ escapeStringForXML lastname)
104+ inTagsSimple "surname" (text $ escapeStringForXML lastname)
86 else -- last name last
105 else -- last name last
87− let namewords = words name
106+ let namewords = words name
88− lengthname = length namewords
107+ lengthname = length namewords
89− (firstname, lastname) = case lengthname of
108+ (firstname, lastname) = case lengthname of
90− 0 -> ("","")
109+ 0 -> ("","")
91− 1 -> ("", name)
110+ 1 -> ("", name)
92− n -> (joinWithSep " " (take (n-1) namewords), last namewords) in
111+ n -> (joinWithSep " " (take (n-1) namewords), last namewords)
93− inTagsSimple "firstname" (text $ escapeStringForXML firstname) $$
112+ in inTagsSimple "firstname" (text $ escapeStringForXML firstname) $$
94− inTagsSimple "surname" (text $ escapeStringForXML lastname)
113+ inTagsSimple "surname" (text $ escapeStringForXML lastname)
95
114
96-- | Convert Pandoc document to string in Docbook format.
115-- | Convert Pandoc document to string in Docbook format.
97writeDocbook :: WriterOptions -> Pandoc -> String
116writeDocbook :: WriterOptions -> Pandoc -> String
98writeDocbook opts (Pandoc (Meta title authors date) blocks) =
117writeDocbook opts (Pandoc (Meta title authors date) blocks) =
99− let head = if (writerStandalone opts)
118+ let head = if writerStandalone opts
100− then text (writerHeader opts)
119+ then text (writerHeader opts)
101− else empty
120+ else empty
102− meta = if (writerStandalone opts)
121+ meta = if writerStandalone opts
103− then inTagsIndented "articleinfo" $
122+ then inTagsIndented "articleinfo" $
104− (inTagsSimple "title" (wrap opts title)) $$
123+ (inTagsSimple "title" (wrap opts title)) $$
105− (vcat (map authorToDocbook authors)) $$
124+ (vcat (map authorToDocbook authors)) $$
106− (inTagsSimple "date" (text $ escapeStringForXML date))
125+ (inTagsSimple "date" (text $ escapeStringForXML date))
107− else empty
126+ else empty
108 elements = hierarchicalize blocks
127 elements = hierarchicalize blocks
109− before = writerIncludeBefore opts
128+ before = writerIncludeBefore opts
110− after = writerIncludeAfter opts
129+ after = writerIncludeAfter opts
111− body = (if null before then empty else text before) $$
130+ body = (if null before then empty else text before) $$
112− vcat (map (elementToDocbook opts) elements) $$
131+ vcat (map (elementToDocbook opts) elements) $$
113− (if null after then empty else text after)
132+ (if null after then empty else text after)
114− body' = if writerStandalone opts
133+ body' = if writerStandalone opts
115− then inTagsIndented "article" (meta $$ body)
134+ then inTagsIndented "article" (meta $$ body)
116− else body in
135+ else body
117− render $ head $$ body' $$ text ""
136+ in render $ head $$ body' $$ text ""
118
137
119-- | Convert an Element to Docbook.
138-- | Convert an Element to Docbook.
120elementToDocbook :: WriterOptions -> Element -> Doc
139elementToDocbook :: WriterOptions -> Element -> Doc
@@ -123,10 +142,10 @@ elementToDocbook opts (Sec title elements) =
123 -- Docbook doesn't allow sections with no content, so insert some if needed
142 -- Docbook doesn't allow sections with no content, so insert some if needed
124 let elements' = if null elements
143 let elements' = if null elements
125 then [Blk (Para [])]
144 then [Blk (Para [])]
126− else elements in
145+ else elements
127− inTagsIndented "section" $
146+ in inTagsIndented "section" $
128− inTagsSimple "title" (wrap opts title) $$
147+ inTagsSimple "title" (wrap opts title) $$
129− vcat (map (elementToDocbook opts) elements')
148+ vcat (map (elementToDocbook opts) elements')
130
149
131-- | Convert a list of Pandoc blocks to Docbook.
150-- | Convert a list of Pandoc blocks to Docbook.
132blocksToDocbook :: WriterOptions -> [Block] -> Doc
151blocksToDocbook :: WriterOptions -> [Block] -> Doc
@@ -145,30 +164,27 @@ deflistItemsToDocbook opts items =
145-- | Convert a term and a list of blocks into a Docbook varlistentry.
164-- | Convert a term and a list of blocks into a Docbook varlistentry.
146deflistItemToDocbook :: WriterOptions -> [Inline] -> [Block] -> Doc
165deflistItemToDocbook :: WriterOptions -> [Inline] -> [Block] -> Doc
147deflistItemToDocbook opts term def =
166deflistItemToDocbook opts term def =
148− let def' = map plainToPara def in
167+ let def' = map plainToPara def
149− inTagsIndented "varlistentry" $
168+ in inTagsIndented "varlistentry" $
150− inTagsIndented "term" (inlinesToDocbook opts term) $$
169+ inTagsIndented "term" (inlinesToDocbook opts term) $$
151− inTagsIndented "listitem" (blocksToDocbook opts def')
170+ inTagsIndented "listitem" (blocksToDocbook opts def')
152
171
153-- | Convert a list of lists of blocks to a list of Docbook list items.
172-- | Convert a list of lists of blocks to a list of Docbook list items.
154listItemsToDocbook :: WriterOptions -> [[Block]] -> Doc
173listItemsToDocbook :: WriterOptions -> [[Block]] -> Doc
155−listItemsToDocbook opts items =
174+listItemsToDocbook opts items = vcat $ map (listItemToDocbook opts) items
156− vcat $ map (listItemToDocbook opts) items
157
175
158-- | Convert a list of blocks into a Docbook list item.
176-- | Convert a list of blocks into a Docbook list item.
159listItemToDocbook :: WriterOptions -> [Block] -> Doc
177listItemToDocbook :: WriterOptions -> [Block] -> Doc
160listItemToDocbook opts item =
178listItemToDocbook opts item =
161− let item' = map plainToPara item in
179+ inTagsIndented "listitem" $ blocksToDocbook opts $ map plainToPara item
162− inTagsIndented "listitem" (blocksToDocbook opts item')
163
180
164-- | Convert a Pandoc block element to Docbook.
181-- | Convert a Pandoc block element to Docbook.
165blockToDocbook :: WriterOptions -> Block -> Doc
182blockToDocbook :: WriterOptions -> Block -> Doc
166blockToDocbook opts Null = empty
183blockToDocbook opts Null = empty
167blockToDocbook opts (Plain lst) = wrap opts lst
184blockToDocbook opts (Plain lst) = wrap opts lst
168−blockToDocbook opts (Para lst) =
185+blockToDocbook opts (Para lst) = inTagsIndented "para" $ wrap opts lst
169− inTagsIndented "para" (wrap opts lst)
170blockToDocbook opts (BlockQuote blocks) =
186blockToDocbook opts (BlockQuote blocks) =
171− inTagsIndented "blockquote" (blocksToDocbook opts blocks)
187+ inTagsIndented "blockquote" $ blocksToDocbook opts blocks
172blockToDocbook opts (CodeBlock str) =
188blockToDocbook opts (CodeBlock str) =
173 text "<screen>\n" <> text (escapeStringForXML str) <> text "\n</screen>"
189 text "<screen>\n" <> text (escapeStringForXML str) <> text "\n</screen>"
174blockToDocbook opts (BulletList lst) =
190blockToDocbook opts (BulletList lst) =
@@ -198,16 +214,16 @@ blockToDocbook opts (Table caption aligns widths headers rows) =
198 then empty
214 then empty
199 else inTagsIndented "caption"
215 else inTagsIndented "caption"
200 (inlinesToDocbook opts caption)
216 (inlinesToDocbook opts caption)
201− tableType = if isEmpty captionDoc then "informaltable" else "table" in
217+ tableType = if isEmpty captionDoc then "informaltable" else "table"
202− inTagsIndented tableType $ captionDoc $$
218+ in inTagsIndented tableType $ captionDoc $$
203− (colHeadsToDocbook opts alignStrings widths headers) $$
219+ (colHeadsToDocbook opts alignStrings widths headers) $$
204− (vcat $ map (tableRowToDocbook opts alignStrings) rows)
220+ (vcat $ map (tableRowToDocbook opts alignStrings) rows)
205
221
206colHeadsToDocbook opts alignStrings widths headers =
222colHeadsToDocbook opts alignStrings widths headers =
207− let heads = zipWith3
223+ let heads = zipWith3 (\align width item ->
208− (\align width item -> tableItemToDocbook opts "th" align width item)
224+ tableItemToDocbook opts "th" align width item)
209− alignStrings widths headers in
225+ alignStrings widths headers
210− inTagsIndented "tr" $ vcat heads
226+ in inTagsIndented "tr" $ vcat heads
211
227
212alignmentToString alignment = case alignment of
228alignmentToString alignment = case alignment of
213 AlignLeft -> "left"
229 AlignLeft -> "left"
@@ -215,20 +231,16 @@ alignmentToString alignment = case alignment of
215 AlignCenter -> "center"
231 AlignCenter -> "center"
216 AlignDefault -> "left"
232 AlignDefault -> "left"
217
233
218−tableRowToDocbook opts aligns cols =
234+tableRowToDocbook opts aligns cols = inTagsIndented "tr" $
219− inTagsIndented "tr" $ vcat $ zipWith3 (tableItemToDocbook opts "td") aligns (repeat 0) cols
235+ vcat $ zipWith3 (tableItemToDocbook opts "td") aligns (repeat 0) cols
220
236
221tableItemToDocbook opts tag align width item =
237tableItemToDocbook opts tag align width item =
222 let attrib = [("align", align)] ++
238 let attrib = [("align", align)] ++
223− if (width /= 0)
239+ if width /= 0
224− then [("style", "{width: " ++
240+ then [("style", "{width: " ++
225− show (truncate (100*width)) ++ "%;}")]
241+ show (truncate (100*width)) ++ "%;}")]
226− else [] in
242+ else []
227− inTags True tag attrib $ vcat $ map (blockToDocbook opts) item
243+ in 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 ++ "]]>"
232
244
233-- | Take list of inline elements and return wrapped doc.
245-- | Take list of inline elements and return wrapped doc.
234wrap :: WriterOptions -> [Inline] -> Doc
246wrap :: WriterOptions -> [Inline] -> Doc
@@ -236,25 +248,24 @@ wrap opts lst = fsep $ map (inlinesToDocbook opts) (splitBy Space lst)
236
248
237-- | Convert a list of inline elements to Docbook.
249-- | Convert a list of inline elements to Docbook.
238inlinesToDocbook :: WriterOptions -> [Inline] -> Doc
250inlinesToDocbook :: WriterOptions -> [Inline] -> Doc
239−inlinesToDocbook opts lst = hcat (map (inlineToDocbook opts) lst)
251+inlinesToDocbook opts lst = hcat $ map (inlineToDocbook opts) lst
240
252
241-- | Convert an inline element to Docbook.
253-- | Convert an inline element to Docbook.
242inlineToDocbook :: WriterOptions -> Inline -> Doc
254inlineToDocbook :: WriterOptions -> Inline -> Doc
243inlineToDocbook opts (Str str) = text $ escapeStringForXML str
255inlineToDocbook opts (Str str) = text $ escapeStringForXML str
244inlineToDocbook opts (Emph lst) =
256inlineToDocbook opts (Emph lst) =
245− inTagsSimple "emphasis" (inlinesToDocbook opts lst)
257+ inTagsSimple "emphasis" $ inlinesToDocbook opts lst
246inlineToDocbook opts (Strong lst) =
258inlineToDocbook opts (Strong lst) =
247− inTags False "emphasis" [("role", "strong")]
259+ inTags False "emphasis" [("role", "strong")] $ inlinesToDocbook opts lst
248− (inlinesToDocbook opts lst)
249inlineToDocbook opts (Strikeout lst) =
260inlineToDocbook opts (Strikeout lst) =
250− inTags False "emphasis" [("role", "strikethrough")]
261+ inTags False "emphasis" [("role", "strikethrough")] $
251− (inlinesToDocbook opts lst)
262+ inlinesToDocbook opts lst
252inlineToDocbook opts (Superscript lst) =
263inlineToDocbook opts (Superscript lst) =
253− inTagsSimple "superscript" (inlinesToDocbook opts lst)
264+ inTagsSimple "superscript" $ inlinesToDocbook opts lst
254inlineToDocbook opts (Subscript lst) =
265inlineToDocbook opts (Subscript lst) =
255− inTagsSimple "subscript" (inlinesToDocbook opts lst)
266+ inTagsSimple "subscript" $ inlinesToDocbook opts lst
256inlineToDocbook opts (Quoted _ lst) =
267inlineToDocbook opts (Quoted _ lst) =
257− inTagsSimple "quote" (inlinesToDocbook opts lst)
268+ inTagsSimple "quote" $ inlinesToDocbook opts lst
258inlineToDocbook opts Apostrophe = char '\''
269inlineToDocbook opts Apostrophe = char '\''
259inlineToDocbook opts Ellipses = text "&#8230;"
270inlineToDocbook opts Ellipses = text "&#8230;"
260inlineToDocbook opts EmDash = text "&#8212;"
271inlineToDocbook opts EmDash = text "&#8212;"
@@ -263,26 +274,24 @@ inlineToDocbook opts (Code str) =
263 inTagsSimple "literal" $ text (escapeStringForXML str)
274 inTagsSimple "literal" $ text (escapeStringForXML str)
264inlineToDocbook opts (TeX str) = inlineToDocbook opts (Code str)
275inlineToDocbook opts (TeX str) = inlineToDocbook opts (Code str)
265inlineToDocbook opts (HtmlInline str) = empty
276inlineToDocbook opts (HtmlInline str) = empty
266−inlineToDocbook opts LineBreak =
277+inlineToDocbook opts LineBreak = text $ "<literallayout></literallayout>"
267− text $ "<literallayout></literallayout>"
268inlineToDocbook opts Space = char ' '
278inlineToDocbook opts Space = char ' '
269inlineToDocbook opts (Link txt (src, tit)) =
279inlineToDocbook opts (Link txt (src, tit)) =
270 if isPrefixOf "mailto:" src
280 if isPrefixOf "mailto:" src
271− then let src' = drop 7 src
281+ then let src' = drop 7 src
272− emailLink = inTagsSimple "email" $ text (escapeStringForXML $ src')
282+ emailLink = inTagsSimple "email" $ text $
273− in if txt == [Code src']
283+ escapeStringForXML $ src'
274− then emailLink
284+ in if txt == [Code src']
275− else inlinesToDocbook opts txt <+> char '(' <> emailLink <>
285+ then emailLink
276− char ')'
286+ else inlinesToDocbook opts txt <+> char '(' <> emailLink <>
277− else inTags False "ulink" [("url", src)] $ inlinesToDocbook opts txt
287+ char ')'
288+ else inTags False "ulink" [("url", src)] $ inlinesToDocbook opts txt
278inlineToDocbook opts (Image alt (src, tit)) =
289inlineToDocbook opts (Image alt (src, tit)) =
279 let titleDoc = if null tit
290 let titleDoc = if null tit
280 then empty
291 then empty
281 else inTagsIndented "objectinfo" $
292 else inTagsIndented "objectinfo" $
282− inTagsIndented "title"
293+ inTagsIndented "title" (text $ escapeStringForXML tit)
283− (text $ escapeStringForXML tit) in
294+ in inTagsIndented "inlinemediaobject" $ inTagsIndented "imageobject" $
284− inTagsIndented "inlinemediaobject" $
295+ titleDoc $$ selfClosingTag "imagedata" [("fileref", src)]
285− inTagsIndented "imageobject" $
286− titleDoc $$ selfClosingTag "imagedata" [("fileref", src)]
287inlineToDocbook opts (Note contents) =
296inlineToDocbook opts (Note contents) =
288 inTagsIndented "footnote" $ blocksToDocbook opts contents
297 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
27
27
28Conversion of 'Pandoc' documents to HTML.
28Conversion of 'Pandoc' documents to HTML.
29-}
29-}
30−module Text.Pandoc.Writers.HTML ( writeHtml, writeHtmlString ) where
30+module Text.Pandoc.Writers.HTML ( writeHtml , writeHtmlString ) where
31import Text.Pandoc.Definition
31import Text.Pandoc.Definition
32import Text.Pandoc.ASCIIMathML
32import Text.Pandoc.ASCIIMathML
33+import Text.Pandoc.CharacterReferences ( decodeCharacterReferences )
33import Text.Pandoc.Shared
34import Text.Pandoc.Shared
34−import Text.Pandoc.Entities (decodeEntities)
35import Text.Regex ( mkRegex, matchRegex )
35import Text.Regex ( mkRegex, matchRegex )
36import Numeric ( showHex )
36import Numeric ( showHex )
37import Data.Char ( ord, toLower )
37import Data.Char ( ord, toLower )
38−import Data.List ( isPrefixOf, partition, intersperse )
38+import Data.List ( isPrefixOf, intersperse )
39import qualified Data.Set as S
39import qualified Data.Set as S
40import Control.Monad.State
40import Control.Monad.State
41import Text.XHtml.Transitional
41import Text.XHtml.Transitional
@@ -55,8 +55,8 @@ defaultWriterState = WriterState {stNotes= [], stIds = [],
55writeHtmlString :: WriterOptions -> Pandoc -> String
55writeHtmlString :: WriterOptions -> Pandoc -> String
56writeHtmlString opts =
56writeHtmlString opts =
57 if writerStandalone opts
57 if writerStandalone opts
58− then renderHtml . (writeHtml opts)
58+ then renderHtml . writeHtml opts
59− else renderHtmlFragment . (writeHtml opts)
59+ else renderHtmlFragment . writeHtml opts
60
60
61-- | Convert Pandoc document to Html structure.
61-- | Convert Pandoc document to Html structure.
62writeHtml :: WriterOptions -> Pandoc -> Html
62writeHtml :: WriterOptions -> Pandoc -> Html
@@ -74,49 +74,51 @@ writeHtml opts (Pandoc (Meta tit authors date) blocks) =
74 map (\a -> meta ! [name "author", content a]) authors) +++
74 map (\a -> meta ! [name "author", content a]) authors) +++
75 (if null date
75 (if null date
76 then noHtml
76 then noHtml
77− else meta ! [name "date", content date])
77+ else meta ! [name "date", content date])
78− titleHeader = if (writerStandalone opts) && (not (null tit)) &&
78+ titleHeader = if writerStandalone opts && not (null tit) &&
79− (not (writerS5 opts))
79+ not (writerS5 opts)
80 then h1 ! [theclass "title"] $ topTitle
80 then h1 ! [theclass "title"] $ topTitle
81 else noHtml
81 else noHtml
82 headerBlocks = filter isHeaderBlock blocks
82 headerBlocks = filter isHeaderBlock blocks
83− ids = uniqueIdentifiers $ map (\(Header _ lst) -> lst) headerBlocks
83+ ids = uniqueIdentifiers $
84− toc = if writerTableOfContents opts
84+ map (\(Header _ lst) -> lst) headerBlocks
85− then tableOfContents opts headerBlocks ids
85+ toc = if writerTableOfContents opts
86− else noHtml
86+ then tableOfContents opts headerBlocks ids
87+ else noHtml
87 (blocks', newstate) =
88 (blocks', newstate) =
88− runState (blockListToHtml opts blocks)
89+ runState (blockListToHtml opts blocks)
89− (defaultWriterState {stIds = ids})
90+ (defaultWriterState {stIds = ids})
90− cssLines = stCSS newstate
91+ cssLines = stCSS newstate
91− css = if S.null cssLines
92+ css = if S.null cssLines
92− then noHtml
93+ then noHtml
93− else style ! [thetype "text/css"] $ primHtml $
94+ else style ! [thetype "text/css"] $ primHtml $
94− '\n':(unlines $ S.toList cssLines)
95+ '\n':(unlines $ S.toList cssLines)
95− math = if stMath newstate
96+ math = if stMath newstate
96− then case writerASCIIMathMLURL opts of
97+ then case writerASCIIMathMLURL opts of
97− Just path -> script ! [src path,
98+ Just path -> script ! [src path,
98− thetype "text/javascript"] $ noHtml
99+ thetype "text/javascript"] $
99− Nothing -> primHtml asciiMathMLScript
100+ noHtml
100− else noHtml
101+ Nothing -> primHtml asciiMathMLScript
101− head = header $ metadata +++ math +++ css +++
102+ else noHtml
102− primHtml (writerHeader opts)
103+ head = header $ metadata +++ math +++ css +++
103− notes = reverse (stNotes newstate)
104+ primHtml (writerHeader opts)
104− before = primHtml $ writerIncludeBefore opts
105+ notes = reverse (stNotes newstate)
105− after = primHtml $ writerIncludeAfter opts
106+ before = primHtml $ writerIncludeBefore opts
106− thebody = before +++ titleHeader +++ toc +++ blocks' +++
107+ after = primHtml $ writerIncludeAfter opts
107− footnoteSection opts notes +++ after
108+ thebody = before +++ titleHeader +++ toc +++ blocks' +++
109+ footnoteSection opts notes +++ after
108 in if writerStandalone opts
110 in if writerStandalone opts
109− then head +++ (body thebody)
111+ then head +++ body thebody
110 else thebody
112 else thebody
111
113
112-- | Construct table of contents from list of header blocks and identifiers.
114-- | Construct table of contents from list of header blocks and identifiers.
113-- Assumes there are as many identifiers as header blocks.
115-- Assumes there are as many identifiers as header blocks.
114tableOfContents :: WriterOptions -> [Block] -> [String] -> Html
116tableOfContents :: WriterOptions -> [Block] -> [String] -> Html
115tableOfContents opts headers ids =
117tableOfContents opts headers ids =
116− let opts' = opts { writerIgnoreNotes = True }
118+ let opts' = opts { writerIgnoreNotes = True }
117 contentsTree = hierarchicalize headers
119 contentsTree = hierarchicalize headers
118− contents = evalState (mapM (elementToListItem opts') contentsTree)
120+ contents = evalState (mapM (elementToListItem opts') contentsTree)
119− (defaultWriterState {stIds = ids})
121+ (defaultWriterState {stIds = ids})
120 in thediv ! [identifier "toc"] $ unordList contents
122 in thediv ! [identifier "toc"] $ unordList contents
121
123
122-- | Converts an Element to a list item for a table of contents,
124-- | Converts an Element to a list item for a table of contents,
@@ -135,7 +137,8 @@ elementToListItem opts (Sec headerText subsecs) = do
135 let subList = if null subHeads
137 let subList = if null subHeads
136 then noHtml
138 then noHtml
137 else unordList subHeads
139 else unordList subHeads
138− return $ (anchor ! [href ("#" ++ id), identifier ("TOC-" ++ id)] $ txt) +++ subList
140+ return $ (anchor ! [href ("#" ++ id), identifier ("TOC-" ++ id)] $ txt) +++
141+ subList
139
142
140-- | Convert list of Note blocks to a footnote <div>.
143-- | Convert list of Note blocks to a footnote <div>.
141-- Assumes notes are sorted.
144-- Assumes notes are sorted.
@@ -143,62 +146,61 @@ footnoteSection :: WriterOptions -> [Html] -> Html
143footnoteSection opts notes =
146footnoteSection opts notes =
144 if null notes
147 if null notes
145 then noHtml
148 then noHtml
146− else thediv ! [theclass "footnotes"] $
149+ else thediv ! [theclass "footnotes"] $ hr +++ (olist << notes)
147− hr +++ (olist << notes)
148
150
149-- | Obfuscate a "mailto:" link using Javascript.
151-- | Obfuscate a "mailto:" link using Javascript.
150obfuscateLink :: WriterOptions -> String -> String -> Html
152obfuscateLink :: WriterOptions -> String -> String -> Html
151obfuscateLink opts text src =
153obfuscateLink opts text src =
152 let emailRegex = mkRegex "^mailto:([^@]*)@(.*)$"
154 let emailRegex = mkRegex "^mailto:([^@]*)@(.*)$"
153− src' = map toLower src in
155+ src' = map toLower src
154− case (matchRegex emailRegex src') of
156+ in case (matchRegex emailRegex src') of
155− (Just [name, domain]) ->
157+ (Just [name, domain]) ->
156− let domain' = substitute "." " dot " domain
158+ let domain' = substitute "." " dot " domain
157− at' = obfuscateChar '@'
159+ at' = obfuscateChar '@'
158− (linkText, altText) =
160+ (linkText, altText) =
159− if text == drop 7 src' -- autolink
161+ if text == drop 7 src' -- autolink
160− then ("'<code>'+e+'</code>'", name ++ " at " ++ domain')
162+ then ("'<code>'+e+'</code>'", name ++ " at " ++ domain')
161− else ("'" ++ text ++ "'", text ++ " (" ++ name ++ " at " ++
163+ else ("'" ++ text ++ "'", text ++ " (" ++ name ++ " at " ++
162− domain' ++ ")") in
164+ domain' ++ ")")
163− if writerStrictMarkdown opts
165+ in if writerStrictMarkdown opts
164− then -- need to use primHtml or &'s are escaped to &amp; in URL
166+ then -- need to use primHtml or &'s are escaped to &amp; in URL
165− primHtml $ "<a href=\"" ++ (obfuscateString src')
167+ primHtml $ "<a href=\"" ++ (obfuscateString src')
166− ++ "\">" ++ (obfuscateString text) ++ "</a>"
168+ ++ "\">" ++ (obfuscateString text) ++ "</a>"
167− else (script ! [thetype "text/javascript"] $
169+ else (script ! [thetype "text/javascript"] $
168− primHtml ("\n<!--\nh='" ++
170+ primHtml ("\n<!--\nh='" ++
169− obfuscateString domain ++ "';a='" ++ at' ++ "';n='" ++
171+ obfuscateString domain ++ "';a='" ++ at' ++ "';n='" ++
170− obfuscateString name ++ "';e=n+a+h;\n" ++
172+ obfuscateString name ++ "';e=n+a+h;\n" ++
171− "document.write('<a h'+'ref'+'=\"ma'+'ilto'+':'+e+'\">'+" ++
173+ "document.write('<a h'+'ref'+'=\"ma'+'ilto'+':'+e+'\">'+" ++
172− linkText ++ "+'<\\/'+'a'+'>');\n// -->\n")) +++
174+ linkText ++ "+'<\\/'+'a'+'>');\n// -->\n")) +++
173− noscript (primHtml $ obfuscateString altText)
175+ noscript (primHtml $ obfuscateString altText)
174− _ -> anchor ! [href src] $ primHtml text -- malformed email
176+ _ -> anchor ! [href src] $ primHtml text -- malformed email
175
177
176-- | Obfuscate character as entity.
178-- | Obfuscate character as entity.
177obfuscateChar :: Char -> String
179obfuscateChar :: Char -> String
178obfuscateChar char =
180obfuscateChar char =
179− let num = ord char in
181+ let num = ord char
180− let numstr = if even num then (show num) else ("x" ++ (showHex num "")) in
182+ numstr = if even num then show num else "x" ++ showHex num ""
181− "&#" ++ numstr ++ ";"
183+ in "&#" ++ numstr ++ ";"
182
184
183-- | Obfuscate string using entities.
185-- | Obfuscate string using entities.
184obfuscateString :: String -> String
186obfuscateString :: String -> String
185−obfuscateString = (concatMap obfuscateChar) . decodeEntities
187+obfuscateString = concatMap obfuscateChar . decodeCharacterReferences
186
188
187-- | True if character is a punctuation character (unicode).
189-- | True if character is a punctuation character (unicode).
188isPunctuation :: Char -> Bool
190isPunctuation :: Char -> Bool
189isPunctuation c =
191isPunctuation c =
190− let c' = ord c in
192+ let c' = ord c
191− if (c `elem` "!\"'()*,-./:;<>?[\\]`{|}~") || (c' >= 0x2000 && c' <= 0x206F) ||
193+ in if c `elem` "!\"'()*,-./:;<>?[\\]`{|}~" || c' >= 0x2000 && c' <= 0x206F ||
192− (c' >= 0xE000 && c' <= 0xE0FF)
194+ c' >= 0xE000 && c' <= 0xE0FF
193− then True
195+ then True
194− else False
196+ else False
195
197
196-- | Add CSS for document header.
198-- | Add CSS for document header.
197addToCSS :: String -> State WriterState ()
199addToCSS :: String -> State WriterState ()
198addToCSS item = do
200addToCSS item = do
199 st <- get
201 st <- get
200 let current = stCSS st
202 let current = stCSS st
201− put $ st {stCSS = (S.insert item current)}
203+ put $ st {stCSS = S.insert item current}
202
204
203-- | Convert Pandoc inline list to plain text identifier.
205-- | Convert Pandoc inline list to plain text identifier.
204inlineListToIdentifier :: [Inline] -> String
206inlineListToIdentifier :: [Inline] -> String
@@ -206,27 +208,26 @@ inlineListToIdentifier [] = ""
206inlineListToIdentifier (x:xs) =
208inlineListToIdentifier (x:xs) =
207 xAsText ++ inlineListToIdentifier xs
209 xAsText ++ inlineListToIdentifier xs
208 where xAsText = case x of
210 where xAsText = case x of
209− Str s -> filter
211+ Str s -> filter (\c -> c == '-' || not (isPunctuation c)) $
210− (\c -> (c == '-') || not (isPunctuation c)) $
212+ concat $ intersperse "-" $ words $ map toLower s
211− concat $ intersperse "-" $ words $ map toLower s
213+ Emph lst -> inlineListToIdentifier lst
212− Emph lst -> inlineListToIdentifier lst
214+ Strikeout lst -> inlineListToIdentifier lst
213− Strikeout lst -> inlineListToIdentifier lst
215+ Superscript lst -> inlineListToIdentifier lst
214− Superscript lst -> inlineListToIdentifier lst
216+ Subscript lst -> inlineListToIdentifier lst
215− Subscript lst -> inlineListToIdentifier lst
217+ Strong lst -> inlineListToIdentifier lst
216− Strong lst -> inlineListToIdentifier lst
218+ Quoted _ lst -> inlineListToIdentifier lst
217− Quoted _ lst -> inlineListToIdentifier lst
219+ Code s -> s
218− Code s -> s
220+ Space -> "-"
219− Space -> "-"
221+ EmDash -> "-"
220− EmDash -> "-"
222+ EnDash -> "-"
221− EnDash -> "-"
223+ Apostrophe -> ""
222− Apostrophe -> ""
224+ Ellipses -> ""
223− Ellipses -> ""
225+ LineBreak -> "-"
224− LineBreak -> "-"
226+ TeX _ -> ""
225− TeX _ -> ""
227+ HtmlInline _ -> ""
226− HtmlInline _ -> ""
228+ Link lst _ -> inlineListToIdentifier lst
227− Link lst _ -> inlineListToIdentifier lst
229+ Image lst _ -> inlineListToIdentifier lst
228− Image lst _ -> inlineListToIdentifier lst
230+ Note _ -> ""
229− Note _ -> ""
230
231
231-- | Return unique identifiers for list of inline lists.
232-- | Return unique identifiers for list of inline lists.
232uniqueIdentifiers :: [[Inline]] -> [String]
233uniqueIdentifiers :: [[Inline]] -> [String]
@@ -236,102 +237,99 @@ uniqueIdentifiers ls =
236 matches = length $ filter (== new) nonuniqueIds
237 matches = length $ filter (== new) nonuniqueIds
237 new' = new ++ if matches > 0 then ("-" ++ show matches) else ""
238 new' = new ++ if matches > 0 then ("-" ++ show matches) else ""
238 in (new:nonuniqueIds, new':uniqueIds)
239 in (new:nonuniqueIds, new':uniqueIds)
239− in reverse $ snd (foldl addIdentifier ([],[]) $ ls)
240+ in reverse $ snd $ foldl addIdentifier ([],[]) ls
240
241
241-- | Convert Pandoc block element to HTML.
242-- | Convert Pandoc block element to HTML.
242blockToHtml :: WriterOptions -> Block -> State WriterState Html
243blockToHtml :: WriterOptions -> Block -> State WriterState Html
243−blockToHtml opts block =
244+blockToHtml opts Null = return $ noHtml
244− case block of
245+blockToHtml opts (Plain lst) = inlineListToHtml opts lst
245− (Null) -> return $ noHtml
246+blockToHtml opts (Para lst) = inlineListToHtml opts lst >>= (return . paragraph)
246− (Plain lst) -> inlineListToHtml opts lst
247+blockToHtml opts (RawHtml str) = return $ primHtml str
247− (Para lst) -> inlineListToHtml opts lst >>= (return . paragraph)
248+blockToHtml opts (HorizontalRule) = return $ hr
248− (RawHtml str) -> return $ primHtml str
249+blockToHtml opts (CodeBlock str) = return $ pre $ thecode << (str ++ "\n")
249− (HorizontalRule) -> return $ hr
250− (CodeBlock str) -> return $ pre $ thecode << (str ++ "\n")
251 -- the final \n for consistency with Markdown.pl
250 -- the final \n for consistency with Markdown.pl
252− (BlockQuote blocks) -> -- in S5, treat list in blockquote specially
251+blockToHtml opts (BlockQuote blocks) =
253− -- if default is incremental, make it nonincremental;
252+ -- in S5, treat list in blockquote specially
254− -- otherwise incremental
253+ -- if default is incremental, make it nonincremental;
255− if writerS5 opts
254+ -- otherwise incremental
256− then let inc = not (writerIncremental opts) in
255+ if writerS5 opts
257− case blocks of
256+ then let inc = not (writerIncremental opts) in
258− [BulletList lst] -> blockToHtml (opts {writerIncremental = inc})
257+ case blocks of
259− (BulletList lst)
258+ [BulletList lst] -> blockToHtml (opts {writerIncremental = inc})
260− [OrderedList attribs lst] ->
259+ (BulletList lst)
261− blockToHtml (opts {writerIncremental = inc})
260+ [OrderedList attribs lst] ->
262− (OrderedList attribs lst)
261+ blockToHtml (opts {writerIncremental = inc})
263− otherwise -> blockListToHtml opts blocks >>=
262+ (OrderedList attribs lst)
264− (return . blockquote)
263+ otherwise -> blockListToHtml opts blocks >>=
265− else blockListToHtml opts blocks >>= (return . blockquote)
264+ (return . blockquote)
266− (Header level lst) -> do contents <- inlineListToHtml opts lst
265+ else blockListToHtml opts blocks >>= (return . blockquote)
267− st <- get
266+blockToHtml opts (Header level lst) = do
268− let ids = stIds st
267+ contents <- inlineListToHtml opts lst
269− let (id, rest) = if null ids
268+ st <- get
270− then ("", [])
269+ let ids = stIds st
271− else (head ids, tail ids)
270+ let (id, rest) = if null ids
272− put $ st {stIds = rest}
271+ then ("", [])
273− let attribs = [identifier id]
272+ else (head ids, tail ids)
274− let headerHtml = case level of
273+ put $ st {stIds = rest}
275− 1 -> h1 contents ! attribs
274+ let attribs = [identifier id]
276− 2 -> h2 contents ! attribs
275+ let headerHtml = case level of
277− 3 -> h3 contents ! attribs
276+ 1 -> h1 contents ! attribs
278− 4 -> h4 contents ! attribs
277+ 2 -> h2 contents ! attribs
279− 5 -> h5 contents ! attribs
278+ 3 -> h3 contents ! attribs
280− 6 -> h6 contents ! attribs
279+ 4 -> h4 contents ! attribs
281− _ -> paragraph contents ! attribs
280+ 5 -> h5 contents ! attribs
282− let headerHtml' = if writerTableOfContents opts
281+ 6 -> h6 contents ! attribs
283− then anchor ! [href ("#TOC-" ++ id)] $
282+ _ -> paragraph contents ! attribs
284− headerHtml
283+ return $ if writerTableOfContents opts
285− else headerHtml
284+ then anchor ! [href ("#TOC-" ++ id)] $ headerHtml
286− return headerHtml'
285+ else headerHtml
287− (BulletList lst) -> do contents <- mapM (blockListToHtml opts) lst
286+blockToHtml opts (BulletList lst) = do
288− let attribs = if writerIncremental opts
287+ contents <- mapM (blockListToHtml opts) lst
289− then [theclass "incremental"]
288+ let attribs = if writerIncremental opts
290− else []
289+ then [theclass "incremental"]
291− return $ unordList ! attribs $ contents
290+ else []
292− (OrderedList (startnum, numstyle, _) lst) -> do
291+ return $ unordList ! attribs $ contents
293− contents <- mapM (blockListToHtml opts) lst
292+blockToHtml opts (OrderedList (startnum, numstyle, _) lst) = do
294− let numstyle' = camelCaseToHyphenated $ show numstyle
293+ contents <- mapM (blockListToHtml opts) lst
295− let attribs = (if writerIncremental opts
294+ let numstyle' = camelCaseToHyphenated $ show numstyle
296− then [theclass "incremental"]
295+ let attribs = (if writerIncremental opts
297− else []) ++
296+ then [theclass "incremental"]
298− (if startnum /= 1
297+ else []) ++
299− then [start startnum]
298+ (if startnum /= 1
300− else []) ++
299+ then [start startnum]
301− (if numstyle /= DefaultStyle
300+ else []) ++
302− then [theclass numstyle']
301+ (if numstyle /= DefaultStyle
303− else [])
302+ then [theclass numstyle']
304− if numstyle /= DefaultStyle
303+ else [])
305− then addToCSS $ "ol." ++ numstyle' ++
304+ if numstyle /= DefaultStyle
306− " { list-style-type: " ++
305+ then addToCSS $ "ol." ++ numstyle' ++
307− numstyle' ++ "; }"
306+ " { list-style-type: " ++
308− else return ()
307+ numstyle' ++ "; }"
309− return $ ordList ! attribs $ contents
308+ else return ()
310− (DefinitionList lst) -> do contents <- mapM (\(term, def) ->
309+ return $ ordList ! attribs $ contents
311− do term' <- inlineListToHtml opts term
310+blockToHtml opts (DefinitionList lst) = do
312− def' <- blockListToHtml opts def
311+ contents <- mapM (\(term, def) -> do term' <- inlineListToHtml opts term
313− return $ (term', def'))
312+ def' <- blockListToHtml opts def
314− lst
313+ return $ (term', def')) lst
315− let attribs = if writerIncremental opts
314+ let attribs = if writerIncremental opts
316− then [theclass "incremental"]
315+ then [theclass "incremental"]
317− else []
316+ else []
318− return $ defList ! attribs $ contents
317+ return $ defList ! attribs $ contents
319− (Table capt aligns widths headers rows) ->
318+blockToHtml opts (Table capt aligns widths headers rows) = do
320− do let alignStrings = map alignmentToString aligns
319+ let alignStrings = map alignmentToString aligns
321− captionDoc <- if null capt
320+ captionDoc <- if null capt
322− then return noHtml
321+ then return noHtml
323− else inlineListToHtml opts capt >>=
322+ else inlineListToHtml opts capt >>= return . caption
324− (return . caption)
323+ colHeads <- colHeadsToHtml opts alignStrings
325− colHeads <- colHeadsToHtml opts alignStrings
324+ widths headers
326− widths headers
325+ rows' <- mapM (tableRowToHtml opts alignStrings) rows
327− rows' <- mapM (tableRowToHtml opts alignStrings) rows
326+ return $ table $ captionDoc +++ colHeads +++ rows'
328− return $ table $ captionDoc +++ colHeads +++ rows'
329
327
330−colHeadsToHtml opts alignStrings widths headers =
328+colHeadsToHtml opts alignStrings widths headers = do
331− do heads <- sequence $ zipWith3
329+ heads <- sequence $ zipWith3
332− (\align width item -> tableItemToHtml opts th align width item)
330+ (\align width item -> tableItemToHtml opts th align width item)
333− alignStrings widths headers
331+ alignStrings widths headers
334− return $ tr $ toHtmlFromList heads
332+ return $ tr $ toHtmlFromList heads
335
333
336alignmentToString alignment = case alignment of
334alignmentToString alignment = case alignment of
337 AlignLeft -> "left"
335 AlignLeft -> "left"
@@ -339,24 +337,27 @@ alignmentToString alignment = case alignment of
339 AlignCenter -> "center"
337 AlignCenter -> "center"
340 AlignDefault -> "left"
338 AlignDefault -> "left"
341
339
342−tableRowToHtml opts aligns cols =
340+tableRowToHtml opts aligns cols =
343− do contents <- sequence $ zipWith3 (tableItemToHtml opts td) aligns (repeat 0) cols
341+ (sequence $ zipWith3 (tableItemToHtml opts td) aligns (repeat 0) cols) >>=
344− return $ tr $ toHtmlFromList contents
342+ return . tr . toHtmlFromList
345
343
346−tableItemToHtml opts tag align' width item =
344+tableItemToHtml opts tag align' width item = do
347− do contents <- blockListToHtml opts item
345+ contents <- blockListToHtml opts item
348− let attrib = [align align'] ++
346+ let attrib = [align align'] ++
349− if (width /= 0)
347+ if width /= 0
350− then [thestyle ("{width: " ++ show (truncate (100*width)) ++ "%;}")]
348+ then [thestyle ("{width: " ++ show (truncate (100*width)) ++
351− else []
349+ "%;}")]
352− return $ tag ! attrib $ contents
350+ else []
351+ return $ tag ! attrib $ contents
353
352
354blockListToHtml :: WriterOptions -> [Block] -> State WriterState Html
353blockListToHtml :: 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
356
356
357-- | Convert list of Pandoc inline elements to HTML.
357-- | Convert list of Pandoc inline elements to HTML.
358inlineListToHtml :: WriterOptions -> [Inline] -> State WriterState Html
358inlineListToHtml :: 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
360
361
361-- | Convert Pandoc inline element to HTML.
362-- | Convert Pandoc inline element to HTML.
362inlineToHtml :: WriterOptions -> Inline -> State WriterState Html
363inlineToHtml :: WriterOptions -> Inline -> State WriterState Html
@@ -369,52 +370,58 @@ inlineToHtml opts inline =
369 (EnDash) -> return $ primHtmlChar "ndash"
370 (EnDash) -> return $ primHtmlChar "ndash"
370 (Ellipses) -> return $ primHtmlChar "hellip"
371 (Ellipses) -> return $ primHtmlChar "hellip"
371 (Apostrophe) -> return $ primHtmlChar "rsquo"
372 (Apostrophe) -> return $ primHtmlChar "rsquo"
372− (Emph lst) -> inlineListToHtml opts lst >>= (return . emphasize)
373+ (Emph lst) -> inlineListToHtml opts lst >>= return . emphasize
373− (Strong lst) -> inlineListToHtml opts lst >>= (return . strong)
374+ (Strong lst) -> inlineListToHtml opts lst >>= return . strong
374 (Code str) -> return $ thecode << str
375 (Code str) -> return $ thecode << str
375− (Strikeout lst) -> addToCSS ".strikeout { text-decoration: line-through; }" >>
376+ (Strikeout lst) -> addToCSS
377+ ".strikeout { text-decoration: line-through; }" >>
376 inlineListToHtml opts lst >>=
378 inlineListToHtml opts lst >>=
377− (return . (thespan ! [theclass "strikeout"]))
379+ return . (thespan ! [theclass "strikeout"])
378− (Superscript lst) -> inlineListToHtml opts lst >>= (return . sup)
380+ (Superscript lst) -> inlineListToHtml opts lst >>= return . sup
379− (Subscript lst) -> inlineListToHtml opts lst >>= (return . sub)
381+ (Subscript lst) -> inlineListToHtml opts lst >>= return . sub
380 (Quoted quoteType lst) ->
382 (Quoted quoteType lst) ->
381 let (leftQuote, rightQuote) = case quoteType of
383 let (leftQuote, rightQuote) = case quoteType of
382 SingleQuote -> (primHtmlChar "lsquo",
384 SingleQuote -> (primHtmlChar "lsquo",
383 primHtmlChar "rsquo")
385 primHtmlChar "rsquo")
384 DoubleQuote -> (primHtmlChar "ldquo",
386 DoubleQuote -> (primHtmlChar "ldquo",
385− primHtmlChar "rdquo") in
387+ primHtmlChar "rdquo")
386− do contents <- inlineListToHtml opts lst
388+ in do contents <- inlineListToHtml opts lst
387− return $ leftQuote +++ contents +++ rightQuote
389+ return $ leftQuote +++ contents +++ rightQuote
388− (TeX str) -> do if writerUseASCIIMathML opts
390+ (TeX str) -> (if writerUseASCIIMathML opts
389− then modify (\st -> st {stMath = True})
391+ then modify (\st -> st {stMath = True})
390− else return ()
392+ else return ()) >> return (stringToHtml str)
391− return $ stringToHtml str
392 (HtmlInline str) -> return $ primHtml str
393 (HtmlInline str) -> return $ primHtml str
393 (Link [Code str] (src,tit)) | "mailto:" `isPrefixOf` src ->
394 (Link [Code str] (src,tit)) | "mailto:" `isPrefixOf` src ->
394− do return $ obfuscateLink opts str src
395+ return $ obfuscateLink opts str src
395− (Link txt (src,tit)) | "mailto:" `isPrefixOf` src ->
396+ (Link txt (src,tit)) | "mailto:" `isPrefixOf` src -> do
396− do linkText <- inlineListToHtml opts txt
397+ linkText <- inlineListToHtml opts txt
397− return $ obfuscateLink opts (show linkText) src
398+ return $ obfuscateLink opts (show linkText) src
398− (Link txt (src,tit)) ->
399+ (Link txt (src,tit)) -> do
399− do linkText <- inlineListToHtml opts txt
400+ linkText <- inlineListToHtml opts txt
400− return $ anchor ! ([href src] ++
401+ return $ anchor ! ([href src] ++
401− if null tit then [] else [title tit]) $ linkText
402+ if null tit then [] else [title tit]) $
402− (Image txt (source,tit)) ->
403+ linkText
403− do alternate <- inlineListToHtml opts txt
404+ (Image txt (source,tit)) -> do
404− let alternate' = renderHtmlFragment alternate
405+ alternate <- inlineListToHtml opts txt
405− let attributes = [src source, title tit] ++
406+ let alternate' = renderHtmlFragment alternate
406− if null txt then [] else [alt alternate']
407+ let attributes = [src source, title tit] ++
407− return $ image ! attributes
408+ if null txt
408− -- note: null title included, as in Markdown.pl
409+ then []
409− (Note contents) -> do st <- get
410+ else [alt alternate']
410− let notes = stNotes st
411+ return $ image ! attributes
411− let number = (length notes) + 1
412+ -- note: null title included, as in Markdown.pl
412− let ref = show number
413+ (Note contents) -> do
413− htmlContents <- blockListToNote opts ref contents
414+ st <- get
414− put $ st {stNotes = (htmlContents:notes)} -- push contents onto front of notes
415+ let notes = stNotes st
415− return $ anchor ! [href ("#fn" ++ ref),
416+ let number = (length notes) + 1
416− theclass "footnoteRef",
417+ let ref = show number
417− identifier ("fnref" ++ ref)] << sup << ref
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
418
425
419blockListToNote :: WriterOptions -> String -> [Block] -> State WriterState Html
426blockListToNote :: WriterOptions -> String -> [Block] -> State WriterState Html
420blockListToNote opts ref blocks =
427blockListToNote opts ref blocks =
@@ -434,6 +441,6 @@ blockListToNote opts ref blocks =
434 [Plain (lst ++ backlink)]
441 [Plain (lst ++ backlink)]
435 _ -> otherBlocks ++ [lastBlock,
442 _ -> otherBlocks ++ [lastBlock,
436 Plain backlink]
443 Plain backlink]
437− in do contents <- blockListToHtml opts blocks'
444+ in do contents <- blockListToHtml opts blocks'
438− return $ li ! [identifier ("fn" ++ ref)] $ contents
445+ return $ li ! [identifier ("fn" ++ ref)] $ contents
439
446
src/Text/Pandoc/Writers/LaTeX.hs+21−24
@@ -27,16 +27,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27
27
28Conversion of 'Pandoc' format into LaTeX.
28Conversion of 'Pandoc' format into LaTeX.
29-}
29-}
30−module Text.Pandoc.Writers.LaTeX (
30+module Text.Pandoc.Writers.LaTeX ( writeLaTeX ) where
31− writeLaTeX
32− ) where
33import Text.Pandoc.Definition
31import Text.Pandoc.Definition
34import Text.Pandoc.Shared
32import Text.Pandoc.Shared
35import Text.Printf ( printf )
33import Text.Printf ( printf )
36import Data.List ( (\\), isInfixOf )
34import Data.List ( (\\), isInfixOf )
35+import Data.Char ( toLower )
37import qualified Data.Set as S
36import qualified Data.Set as S
38import Control.Monad.State
37import Control.Monad.State
39−import Data.Char ( toLower )
40
38
41data WriterState =
39data WriterState =
42 WriterState { stIncludes :: S.Set String -- strings to include in header
40 WriterState { stIncludes :: S.Set String -- strings to include in header
@@ -77,16 +75,16 @@ latexHeader :: WriterOptions -- ^ Options, including LaTeX header
77 -> Meta -- ^ Meta with bibliographic information
75 -> Meta -- ^ Meta with bibliographic information
78 -> State WriterState String
76 -> State WriterState String
79latexHeader options (Meta title authors date) = do
77latexHeader options (Meta title authors date) = do
80− titletext <- if null title
78+ titletext <- if null title
81− then return ""
79+ then return ""
82− else do title' <- inlineListToLaTeX title
80+ else do title' <- inlineListToLaTeX title
83− return $ "\\title{" ++ title' ++ "}\n"
81+ return $ "\\title{" ++ title' ++ "}\n"
84− extras <- get >>= (return . unlines . S.toList. stIncludes)
82+ extras <- get >>= (return . unlines . S.toList. stIncludes)
85 let verbatim = if "\\usepackage{fancyvrb}" `isInfixOf` extras
83 let verbatim = if "\\usepackage{fancyvrb}" `isInfixOf` extras
86 then "\\VerbatimFootnotes % allows verbatim text in footnotes\n"
84 then "\\VerbatimFootnotes % allows verbatim text in footnotes\n"
87 else ""
85 else ""
88− let authorstext = "\\author{" ++ (joinWithSep "\\\\"
86+ let authorstext = "\\author{" ++
89− (map stringToLaTeX authors)) ++ "}\n"
87+ joinWithSep "\\\\" (map stringToLaTeX authors) ++ "}\n"
90 let datetext = if date == ""
88 let datetext = if date == ""
91 then ""
89 then ""
92 else "\\date{" ++ stringToLaTeX date ++ "}\n"
90 else "\\date{" ++ stringToLaTeX date ++ "}\n"
@@ -124,8 +122,8 @@ deVerb (other:rest) = other:(deVerb rest)
124blockToLaTeX :: Block -- ^ Block to convert
122blockToLaTeX :: Block -- ^ Block to convert
125 -> State WriterState String
123 -> State WriterState String
126blockToLaTeX Null = return ""
124blockToLaTeX Null = return ""
127−blockToLaTeX (Plain lst) = (inlineListToLaTeX lst) >>= (return . (++ "\n"))
125+blockToLaTeX (Plain lst) = inlineListToLaTeX lst >>= return . (++ "\n")
128−blockToLaTeX (Para lst) = (inlineListToLaTeX lst) >>= (return . (++ "\n\n"))
126+blockToLaTeX (Para lst) = inlineListToLaTeX lst >>= return . (++ "\n\n")
129blockToLaTeX (BlockQuote lst) = do
127blockToLaTeX (BlockQuote lst) = do
130 contents <- blockListToLaTeX lst
128 contents <- blockListToLaTeX lst
131 return $ "\\begin{quote}\n" ++ contents ++ "\\end{quote}\n"
129 return $ "\\begin{quote}\n" ++ contents ++ "\\end{quote}\n"
@@ -184,22 +182,22 @@ blockToLaTeX (Table caption aligns widths heads rows) = do
184 colWidths aligns
182 colWidths aligns
185 let tableBody = "\\begin{tabular}{" ++ colDescriptors ++ "}\n" ++
183 let tableBody = "\\begin{tabular}{" ++ colDescriptors ++ "}\n" ++
186 headers ++ "\\hline\n" ++ concat rows' ++ "\\end{tabular}\n"
184 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"
188 addToHeader "\\usepackage{array}\n\
186 addToHeader "\\usepackage{array}\n\
189− \% This is needed because raggedright in table elements redefines \\\\:\n\
187+ \% This is needed because raggedright in table elements redefines \\\\:\n\
190− \\\newcommand{\\PreserveBackslash}[1]{\\let\\temp=\\\\#1\\let\\\\=\\temp}\n\
188+ \\\newcommand{\\PreserveBackslash}[1]{\\let\\temp=\\\\#1\\let\\\\=\\temp}\n\
191− \\\let\\PBS=\\PreserveBackslash"
189+ \\\let\\PBS=\\PreserveBackslash"
192 return $ if null captionText
190 return $ if null captionText
193 then centered tableBody ++ "\n"
191 then centered tableBody ++ "\n"
194− else "\\begin{table}[h]\n" ++ centered tableBody ++ "\\caption{" ++
192+ else "\\begin{table}[h]\n" ++ centered tableBody ++
195− captionText ++ "}\n" ++ "\\end{table}\n\n"
193+ "\\caption{" ++ captionText ++ "}\n" ++ "\\end{table}\n\n"
196
194
197−blockListToLaTeX lst = mapM blockToLaTeX lst >>= (return . concat)
195+blockListToLaTeX lst = mapM blockToLaTeX lst >>= return . concat
198
196
199tableRowToLaTeX cols =
197tableRowToLaTeX cols =
200− mapM blockListToLaTeX cols >>= (return . (++ "\\\\\n") . (joinWithSep " & "))
198+ mapM blockListToLaTeX cols >>= return . (++ "\\\\\n") . (joinWithSep " & ")
201
199
202−listItemToLaTeX lst = blockListToLaTeX lst >>= (return . ("\\item "++))
200+listItemToLaTeX lst = blockListToLaTeX lst >>= return . ("\\item "++)
203
201
204defListItemToLaTeX (term, def) = do
202defListItemToLaTeX (term, def) = do
205 term' <- inlineListToLaTeX $ deVerb term
203 term' <- inlineListToLaTeX $ deVerb term
@@ -209,8 +207,7 @@ defListItemToLaTeX (term, def) = do
209-- | Convert list of inline elements to LaTeX.
207-- | Convert list of inline elements to LaTeX.
210inlineListToLaTeX :: [Inline] -- ^ Inlines to convert
208inlineListToLaTeX :: [Inline] -- ^ Inlines to convert
211 -> State WriterState String
209 -> State WriterState String
212−inlineListToLaTeX lst =
210+inlineListToLaTeX lst = mapM inlineToLaTeX lst >>= return . concat
213− mapM inlineToLaTeX lst >>= (return . concat)
214
211
215isQuoted :: Inline -> Bool
212isQuoted :: Inline -> Bool
216isQuoted (Quoted _ _) = True
213isQuoted (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
28Conversion of 'Pandoc' documents to groff man page format.
28Conversion of 'Pandoc' documents to groff man page format.
29
29
30-}
30-}
31−module Text.Pandoc.Writers.Man (
31+module Text.Pandoc.Writers.Man ( writeMan) where
32− writeMan
33− ) where
34import Text.Pandoc.Definition
32import Text.Pandoc.Definition
35import Text.Pandoc.Shared
33import Text.Pandoc.Shared
36import Text.Printf ( printf )
34import Text.Printf ( printf )
37−import Data.Char ( toUpper )
35+import Data.List ( isPrefixOf, drop, nub, intersperse )
38−import Data.List ( group, isPrefixOf, drop, find, nub, intersperse )
39import Text.PrettyPrint.HughesPJ hiding ( Str )
36import Text.PrettyPrint.HughesPJ hiding ( Str )
40import Control.Monad.State
37import Control.Monad.State
41
38
@@ -45,16 +42,15 @@ type WriterState = (Notes, Preprocessors)
45
42
46-- | Convert Pandoc to Man.
43-- | Convert Pandoc to Man.
47writeMan :: WriterOptions -> Pandoc -> String
44writeMan :: WriterOptions -> Pandoc -> String
48−writeMan opts document =
45+writeMan opts document = render $ evalState (pandocToMan opts document) ([],[])
49− render $ evalState (pandocToMan opts document) ([],[])
50
46
51-- | Return groff man representation of document.
47-- | Return groff man representation of document.
52pandocToMan :: WriterOptions -> Pandoc -> State WriterState Doc
48pandocToMan :: WriterOptions -> Pandoc -> State WriterState Doc
53pandocToMan opts (Pandoc meta blocks) = do
49pandocToMan opts (Pandoc meta blocks) = do
54 let before = writerIncludeBefore opts
50 let before = writerIncludeBefore opts
55 let after = writerIncludeAfter opts
51 let after = writerIncludeAfter opts
56− before' = if null before then empty else text before
52+ let before' = if null before then empty else text before
57− after' = if null after then empty else text after
53+ let after' = if null after then empty else text after
58 (head, foot) <- metaToMan opts meta
54 (head, foot) <- metaToMan opts meta
59 body <- blockListToMan opts blocks
55 body <- blockListToMan opts blocks
60 (notes, preprocessors) <- get
56 (notes, preprocessors) <- get
@@ -84,8 +80,8 @@ metaToMan options (Meta title authors date) = do
84 1 -> text ".SH AUTHOR" $$ (text $ joinWithSep ", " authors)
80 1 -> text ".SH AUTHOR" $$ (text $ joinWithSep ", " authors)
85 2 -> text ".SH AUTHORS" $$ (text $ joinWithSep ", " authors)
81 2 -> text ".SH AUTHORS" $$ (text $ joinWithSep ", " authors)
86 return $ if writerStandalone options
82 return $ if writerStandalone options
87− then (head, foot)
83+ then (head, foot)
88− else (empty, empty)
84+ else (empty, empty)
89
85
90-- | Return man representation of notes.
86-- | Return man representation of notes.
91notesToMan :: WriterOptions -> [[Block]] -> State WriterState Doc
87notesToMan :: WriterOptions -> [[Block]] -> State WriterState Doc
@@ -93,7 +89,7 @@ notesToMan opts notes =
93 if null notes
89 if null notes
94 then return empty
90 then return empty
95 else mapM (\(num, note) -> noteToMan opts num note) (zip [1..] notes) >>=
91 else mapM (\(num, note) -> noteToMan opts num note) (zip [1..] notes) >>=
96− (return . (text ".SH NOTES" $$) . vcat)
92+ return . (text ".SH NOTES" $$) . vcat
97
93
98-- | Return man representation of a note.
94-- | Return man representation of a note.
99noteToMan :: WriterOptions -> Int -> [Block] -> State WriterState Doc
95noteToMan :: WriterOptions -> Int -> [Block] -> State WriterState Doc
@@ -110,8 +106,7 @@ wrappedMan opts sect = do
110
106
111-- | Association list of characters to escape.
107-- | Association list of characters to escape.
112manEscapes :: [(Char, String)]
108manEscapes :: [(Char, String)]
113−manEscapes = [('\160', "\\ "), ('\'', "\\[aq]")] ++
109+manEscapes = [('\160', "\\ "), ('\'', "\\[aq]")] ++ backslashEscapes "\".@\\"
114− backslashEscapes "\".@\\"
115
110
116-- | Escape special characters for Man.
111-- | Escape special characters for Man.
117escapeString :: String -> String
112escapeString :: String -> String
@@ -140,8 +135,7 @@ blockToMan opts (Header level inlines) = do
140 return $ text heading <> contents
135 return $ text heading <> contents
141blockToMan opts (CodeBlock str) = return $
136blockToMan opts (CodeBlock str) = return $
142 text ".PP" $$ text "\\f[CR]" $$
137 text ".PP" $$ text "\\f[CR]" $$
143− text ((unlines . map (" " ++) . lines) (escapeCode str)) <>
138+ text ((unlines . map (" " ++) . lines) (escapeCode str)) <> text "\\f[]"
144− text "\\f[]"
145blockToMan opts (BlockQuote blocks) = do
139blockToMan opts (BlockQuote blocks) = do
146 contents <- blockListToMan opts blocks
140 contents <- blockListToMan opts blocks
147 return $ text ".RS" $$ contents $$ text ".RE"
141 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.
29
29
30Markdown: <http://daringfireball.net/projects/markdown/>
30Markdown: <http://daringfireball.net/projects/markdown/>
31-}
31-}
32−module Text.Pandoc.Writers.Markdown (
32+module Text.Pandoc.Writers.Markdown ( writeMarkdown) where
33− writeMarkdown
34− ) where
35import Text.Pandoc.Definition
33import Text.Pandoc.Definition
36import Text.Pandoc.Shared
34import Text.Pandoc.Shared
37import Text.Pandoc.Blocks
35import Text.Pandoc.Blocks
@@ -53,10 +51,10 @@ pandocToMarkdown :: WriterOptions -> Pandoc -> State WriterState Doc
53pandocToMarkdown opts (Pandoc meta blocks) = do
51pandocToMarkdown opts (Pandoc meta blocks) = do
54 let before = writerIncludeBefore opts
52 let before = writerIncludeBefore opts
55 let after = writerIncludeAfter opts
53 let after = writerIncludeAfter opts
56− before' = if null before then empty else text before
54+ let before' = if null before then empty else text before
57− after' = if null after then empty else text after
55+ let after' = if null after then empty else text after
58 metaBlock <- metaToMarkdown opts meta
56 metaBlock <- metaToMarkdown opts meta
59− let head = if (writerStandalone opts)
57+ let head = if writerStandalone opts
60 then metaBlock $+$ text (writerHeader opts)
58 then metaBlock $+$ text (writerHeader opts)
61 else empty
59 else empty
62 let headerBlocks = filter isHeaderBlock blocks
60 let headerBlocks = filter isHeaderBlock blocks
@@ -73,8 +71,7 @@ pandocToMarkdown opts (Pandoc meta blocks) = do
73
71
74-- | Return markdown representation of reference key table.
72-- | Return markdown representation of reference key table.
75keyTableToMarkdown :: WriterOptions -> KeyTable -> State WriterState Doc
73keyTableToMarkdown :: WriterOptions -> KeyTable -> State WriterState Doc
76−keyTableToMarkdown opts refs =
74+keyTableToMarkdown opts refs = mapM (keyToMarkdown opts) refs >>= return . vcat
77− mapM (keyToMarkdown opts) refs >>= (return . vcat)
78
75
79-- | Return markdown representation of a reference key.
76-- | Return markdown representation of a reference key.
80keyToMarkdown :: WriterOptions
77keyToMarkdown :: WriterOptions
@@ -90,7 +87,7 @@ keyToMarkdown opts (label, (src, tit)) = do
90notesToMarkdown :: WriterOptions -> [[Block]] -> State WriterState Doc
87notesToMarkdown :: WriterOptions -> [[Block]] -> State WriterState Doc
91notesToMarkdown opts notes =
88notesToMarkdown opts notes =
92 mapM (\(num, note) -> noteToMarkdown opts num note) (zip [1..] notes) >>=
89 mapM (\(num, note) -> noteToMarkdown opts num note) (zip [1..] notes) >>=
93− (return . vcat)
90+ return . vcat
94
91
95-- | Return markdown representation of a note.
92-- | Return markdown representation of a note.
96noteToMarkdown :: WriterOptions -> Int -> [Block] -> State WriterState Doc
93noteToMarkdown :: WriterOptions -> Int -> [Block] -> State WriterState Doc
@@ -143,8 +140,7 @@ tableOfContents opts headers =
143-- | Converts an Element to a list item for a table of contents,
140-- | Converts an Element to a list item for a table of contents,
144elementToListItem :: Element -> [Block]
141elementToListItem :: Element -> [Block]
145elementToListItem (Blk _) = []
142elementToListItem (Blk _) = []
146−elementToListItem (Sec headerText subsecs) =
143+elementToListItem (Sec headerText subsecs) = [Plain headerText] ++
147− [Plain headerText] ++
148 if null subsecs
144 if null subsecs
149 then []
145 then []
150 else [BulletList $ map elementToListItem subsecs]
146 else [BulletList $ map elementToListItem subsecs]
@@ -184,9 +180,8 @@ blockToMarkdown opts (Table caption aligns widths headers rows) = do
184 let makeRow = hsepBlocks . (zipWith alignHeader aligns) .
180 let makeRow = hsepBlocks . (zipWith alignHeader aligns) .
185 (zipWith docToBlock widthsInChars)
181 (zipWith docToBlock widthsInChars)
186 let head = makeRow headers'
182 let head = makeRow headers'
187− rows' <- mapM (\row -> do
183+ rows' <- mapM (\row -> do cols <- mapM (blockListToMarkdown opts) row
188− cols <- mapM (blockListToMarkdown opts) row
184+ return $ makeRow cols) rows
189− return $ makeRow cols) rows
190 let tableWidth = sum widthsInChars
185 let tableWidth = sum widthsInChars
191 let maxRowHeight = maximum $ map heightOfBlock (head:rows')
186 let maxRowHeight = maximum $ map heightOfBlock (head:rows')
192 let isMultilineTable = maxRowHeight > 1
187 let isMultilineTable = maxRowHeight > 1
@@ -208,8 +203,7 @@ blockToMarkdown opts (OrderedList attribs items) = do
208 let markers = orderedListMarkers attribs
203 let markers = orderedListMarkers attribs
209 let markers' = map (\m -> if length m < 3
204 let markers' = map (\m -> if length m < 3
210 then m ++ replicate (3 - length m) ' '
205 then m ++ replicate (3 - length m) ' '
211− else m)
206+ else m) markers
212− markers
213 contents <- mapM (\(item, num) -> orderedListItemToMarkdown opts item num) $
207 contents <- mapM (\(item, num) -> orderedListItemToMarkdown opts item num) $
214 zip markers' items
208 zip markers' items
215 return $ (vcat contents) <> text "\n"
209 return $ (vcat contents) <> text "\n"
@@ -241,8 +235,8 @@ definitionListItemToMarkdown opts (label, items) = do
241 let tabStop = writerTabStop opts
235 let tabStop = writerTabStop opts
242 let leader = char ':'
236 let leader = char ':'
243 contents <- mapM (\item -> blockToMarkdown opts item >>=
237 contents <- mapM (\item -> blockToMarkdown opts item >>=
244− (\txt -> return (leader $$ nest tabStop txt)))
238+ (\txt -> return (leader $$ nest tabStop txt)))
245− items >>= (return . vcat)
239+ items >>= return . vcat
246 return $ labelText $+$ contents
240 return $ labelText $+$ contents
247
241
248-- | Convert list of Pandoc block elements to markdown.
242-- | Convert list of Pandoc block elements to markdown.
@@ -250,29 +244,30 @@ blockListToMarkdown :: WriterOptions -- ^ Options
250 -> [Block] -- ^ List of block elements
244 -> [Block] -- ^ List of block elements
251 -> State WriterState Doc
245 -> State WriterState Doc
252blockListToMarkdown opts blocks =
246blockListToMarkdown opts blocks =
253− mapM (blockToMarkdown opts) blocks >>= (return . vcat)
247+ mapM (blockToMarkdown opts) blocks >>= return . vcat
254
248
255-- | Get reference for target; if none exists, create unique one and return.
249-- | Get reference for target; if none exists, create unique one and return.
256-- Prefer label if possible; otherwise, generate a unique key.
250-- Prefer label if possible; otherwise, generate a unique key.
257getReference :: [Inline] -> Target -> State WriterState [Inline]
251getReference :: [Inline] -> Target -> State WriterState [Inline]
258getReference label (src, tit) = do
252getReference label (src, tit) = do
259− (_,refs) <- get
253+ (_,refs) <- get
260− case find ((== (src, tit)) . snd) refs of
254+ case find ((== (src, tit)) . snd) refs of
261− Just (ref, _) -> return ref
255+ Just (ref, _) -> return ref
262− Nothing -> do
256+ Nothing -> do
263− let label' = case find ((== label) . fst) refs of
257+ let label' = case find ((== label) . fst) refs of
264− Just _ -> -- label is used; generate numerical label
258+ Just _ -> -- label is used; generate numerical label
265− case find (\n -> not (any (== [Str (show n)])
259+ case find (\n -> not (any (== [Str (show n)])
266− (map fst refs))) [1..10000] of
260+ (map fst refs))) [1..10000] of
267− Just x -> [Str (show x)]
261+ Just x -> [Str (show x)]
268− Nothing -> error "no unique label"
262+ Nothing -> error "no unique label"
269− Nothing -> label
263+ Nothing -> label
270− modify (\(notes, refs) -> (notes, (label', (src,tit)):refs))
264+ modify (\(notes, refs) -> (notes, (label', (src,tit)):refs))
271− return label'
265+ return label'
272
266
273-- | Convert list of Pandoc inline elements to markdown.
267-- | Convert list of Pandoc inline elements to markdown.
274inlineListToMarkdown :: WriterOptions -> [Inline] -> State WriterState Doc
268inlineListToMarkdown :: 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
276
271
277-- | Convert Pandoc inline element to markdown.
272-- | Convert Pandoc inline element to markdown.
278inlineToMarkdown :: WriterOptions -> Inline -> State WriterState Doc
273inlineToMarkdown :: WriterOptions -> Inline -> State WriterState Doc
@@ -327,13 +322,13 @@ inlineToMarkdown opts (Link txt (src, tit)) = do
327 return $ if useAuto
322 return $ if useAuto
328 then char '<' <> text srcSuffix <> char '>'
323 then char '<' <> text srcSuffix <> char '>'
329 else if useRefLinks
324 else if useRefLinks
330− then let first = char '[' <> linktext <> char ']'
325+ then let first = char '[' <> linktext <> char ']'
331− second = if txt == ref
326+ second = if txt == ref
332− then text "[]"
327+ then text "[]"
333− else char '[' <> reftext <> char ']'
328+ else char '[' <> reftext <> char ']'
334− in first <> second
329+ in first <> second
335− else char '[' <> linktext <> char ']' <>
330+ else char '[' <> linktext <> char ']' <>
336− char '(' <> text src <> linktitle <> char ')'
331+ char '(' <> text src <> linktitle <> char ')'
337inlineToMarkdown opts (Image alternate (source, tit)) = do
332inlineToMarkdown opts (Image alternate (source, tit)) = do
338 let txt = if (null alternate) || (alternate == [Str ""]) ||
333 let txt = if (null alternate) || (alternate == [Str ""]) ||
339 (alternate == [Str source]) -- to prevent autolinks
334 (alternate == [Str source]) -- to prevent autolinks
src/Text/Pandoc/Writers/RST.hs+19−27
@@ -29,13 +29,11 @@ Conversion of 'Pandoc' documents to reStructuredText.
29
29
30reStructuredText: <http://docutils.sourceforge.net/rst.html>
30reStructuredText: <http://docutils.sourceforge.net/rst.html>
31-}
31-}
32−module Text.Pandoc.Writers.RST (
32+module Text.Pandoc.Writers.RST ( writeRST) where
33− writeRST
34− ) where
35import Text.Pandoc.Definition
33import Text.Pandoc.Definition
36import Text.Pandoc.Shared
34import Text.Pandoc.Shared
37import Text.Pandoc.Blocks
35import Text.Pandoc.Blocks
38−import Data.List ( group, isPrefixOf, drop, find, intersperse )
36+import Data.List ( isPrefixOf, isSuffixOf, drop, intersperse )
39import Text.PrettyPrint.HughesPJ hiding ( Str )
37import Text.PrettyPrint.HughesPJ hiding ( Str )
40import Control.Monad.State
38import Control.Monad.State
41
39
@@ -70,8 +68,7 @@ pandocToRST opts (Pandoc meta blocks) = do
70
68
71-- | Return RST representation of reference key table.
69-- | Return RST representation of reference key table.
72keyTableToRST :: WriterOptions -> KeyTable -> State WriterState Doc
70keyTableToRST :: WriterOptions -> KeyTable -> State WriterState Doc
73−keyTableToRST opts refs =
71+keyTableToRST opts refs = mapM (keyToRST opts) refs >>= return . vcat
74− mapM (keyToRST opts) refs >>= (return . vcat)
75
72
76-- | Return RST representation of a reference key.
73-- | Return RST representation of a reference key.
77keyToRST :: WriterOptions
74keyToRST :: WriterOptions
@@ -85,7 +82,7 @@ keyToRST opts (label, (src, tit)) = do
85notesToRST :: WriterOptions -> [[Block]] -> State WriterState Doc
82notesToRST :: WriterOptions -> [[Block]] -> State WriterState Doc
86notesToRST opts notes =
83notesToRST opts notes =
87 mapM (\(num, note) -> noteToRST opts num note) (zip [1..] notes) >>=
84 mapM (\(num, note) -> noteToRST opts num note) (zip [1..] notes) >>=
88− (return . vcat)
85+ return . vcat
89
86
90-- | Return RST representation of a note.
87-- | Return RST representation of a note.
91noteToRST :: WriterOptions -> Int -> [Block] -> State WriterState Doc
88noteToRST :: WriterOptions -> Int -> [Block] -> State WriterState Doc
@@ -96,8 +93,7 @@ noteToRST opts num note = do
96
93
97-- | Return RST representation of picture reference table.
94-- | Return RST representation of picture reference table.
98pictTableToRST :: WriterOptions -> KeyTable -> State WriterState Doc
95pictTableToRST :: WriterOptions -> KeyTable -> State WriterState Doc
99−pictTableToRST opts refs =
96+pictTableToRST opts refs = mapM (pictToRST opts) refs >>= return . vcat
100− mapM (pictToRST opts) refs >>= (return . vcat)
101
97
102-- | Return RST representation of a picture substitution reference.
98-- | Return RST representation of a picture substitution reference.
103pictToRST :: WriterOptions
99pictToRST :: WriterOptions
@@ -112,7 +108,7 @@ pictToRST opts (label, (src, _)) = do
112wrappedRST :: WriterOptions -> [Inline] -> State WriterState Doc
108wrappedRST :: WriterOptions -> [Inline] -> State WriterState Doc
113wrappedRST opts inlines =
109wrappedRST opts inlines =
114 mapM (wrappedRSTSection opts) (splitBy LineBreak inlines) >>=
110 mapM (wrappedRSTSection opts) (splitBy LineBreak inlines) >>=
115− (return . vcat)
111+ return . vcat
116
112
117wrappedRSTSection :: WriterOptions -> [Inline] -> State WriterState Doc
113wrappedRSTSection :: WriterOptions -> [Inline] -> State WriterState Doc
118wrappedRSTSection opts sect = do
114wrappedRSTSection opts sect = do
@@ -160,21 +156,19 @@ blockToRST :: WriterOptions -- ^ Options
160blockToRST opts Null = return empty
156blockToRST opts Null = return empty
161blockToRST opts (Plain inlines) = wrappedRST opts inlines
157blockToRST opts (Plain inlines) = wrappedRST opts inlines
162blockToRST opts (Para [TeX str]) =
158blockToRST opts (Para [TeX str]) =
163− let str' = if (endsWith '\n' str) then (str ++ "\n") else (str ++ "\n\n") in
159+ let str' = if "\n" `isSuffixOf` str then str ++ "\n" else str ++ "\n\n" in
164− return $ hang (text "\n.. raw:: latex\n") 3
160+ return $ hang (text "\n.. raw:: latex\n") 3 $ vcat $ map text (lines str')
165− (vcat $ map text (lines str'))
166blockToRST opts (Para inlines) = do
161blockToRST opts (Para inlines) = do
167 contents <- wrappedRST opts inlines
162 contents <- wrappedRST opts inlines
168 return $ contents <> text "\n"
163 return $ contents <> text "\n"
169blockToRST opts (RawHtml str) =
164blockToRST opts (RawHtml str) =
170− let str' = if (endsWith '\n' str) then (str ++ "\n") else (str ++ "\n\n") in
165+ let str' = if "\n" `isSuffixOf` str then str ++ "\n" else str ++ "\n\n" in
171− return $ hang (text "\n.. raw:: html\n") 3
166+ return $ hang (text "\n.. raw:: html\n") 3 $ vcat $ map text (lines str')
172− (vcat $ map text (lines str'))
173blockToRST opts HorizontalRule = return $ text "--------------\n"
167blockToRST opts HorizontalRule = return $ text "--------------\n"
174blockToRST opts (Header level inlines) = do
168blockToRST opts (Header level inlines) = do
175 contents <- inlineListToRST opts inlines
169 contents <- inlineListToRST opts inlines
176 let headerLength = length $ render contents
170 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)
178 let border = text $ replicate headerLength headerChar
172 let border = text $ replicate headerLength headerChar
179 return $ contents $+$ border <> text "\n"
173 return $ contents $+$ border <> text "\n"
180blockToRST opts (CodeBlock str) = return $ (text "::\n") $+$
174blockToRST opts (CodeBlock str) = return $ (text "::\n") $+$
@@ -200,11 +194,10 @@ blockToRST opts (Table caption aligns widths headers rows) = do
200 beg = TextBlock 2 height (replicate height "| ")
194 beg = TextBlock 2 height (replicate height "| ")
201 end = TextBlock 2 height (replicate height " |")
195 end = TextBlock 2 height (replicate height " |")
202 middle = hcatBlocks $ intersperse sep blocks
196 middle = hcatBlocks $ intersperse sep blocks
203− let makeRow = hpipeBlocks . (zipWith docToBlock widthsInChars)
197+ let makeRow = hpipeBlocks . zipWith docToBlock widthsInChars
204 let head = makeRow headers'
198 let head = makeRow headers'
205− rows' <- mapM (\row -> do
199+ rows' <- mapM (\row -> do cols <- mapM (blockListToRST opts) row
206− cols <- mapM (blockListToRST opts) row
200+ return $ makeRow cols) rows
207− return $ makeRow cols) rows
208 let tableWidth = sum widthsInChars
201 let tableWidth = sum widthsInChars
209 let maxRowHeight = maximum $ map heightOfBlock (head:rows')
202 let maxRowHeight = maximum $ map heightOfBlock (head:rows')
210 let border ch = char '+' <> char ch <>
203 let border ch = char '+' <> char ch <>
@@ -225,8 +218,7 @@ blockToRST opts (OrderedList (start, style, delim) items) = do
225 (start, style, delim)
218 (start, style, delim)
226 let maxMarkerLength = maximum $ map length markers
219 let maxMarkerLength = maximum $ map length markers
227 let markers' = map (\m -> let s = maxMarkerLength - length m
220 let markers' = map (\m -> let s = maxMarkerLength - length m
228− in m ++ replicate s ' ')
221+ in m ++ replicate s ' ') markers
229− markers
230 contents <- mapM (\(item, num) -> orderedListItemToRST opts item num) $
222 contents <- mapM (\(item, num) -> orderedListItemToRST opts item num) $
231 zip markers' items
223 zip markers' items
232 -- ensure that sublists have preceding blank line
224 -- ensure that sublists have preceding blank line
@@ -262,11 +254,11 @@ blockListToRST :: WriterOptions -- ^ Options
262 -> [Block] -- ^ List of block elements
254 -> [Block] -- ^ List of block elements
263 -> State WriterState Doc
255 -> State WriterState Doc
264blockListToRST opts blocks =
256blockListToRST opts blocks =
265− mapM (blockToRST opts) blocks >>= (return . vcat)
257+ mapM (blockToRST opts) blocks >>= return . vcat
266
258
267-- | Convert list of Pandoc inline elements to RST.
259-- | Convert list of Pandoc inline elements to RST.
268inlineListToRST :: WriterOptions -> [Inline] -> State WriterState Doc
260inlineListToRST :: 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
270
262
271-- | Convert Pandoc inline element to RST.
263-- | Convert Pandoc inline element to RST.
272inlineToRST :: WriterOptions -> Inline -> State WriterState Doc
264inlineToRST :: WriterOptions -> Inline -> State WriterState Doc
@@ -319,8 +311,8 @@ inlineToRST opts (Link txt (src, tit)) = do
319inlineToRST opts (Image alternate (source, tit)) = do
311inlineToRST opts (Image alternate (source, tit)) = do
320 (notes, refs, pics) <- get
312 (notes, refs, pics) <- get
321 let labelsUsed = map fst pics
313 let labelsUsed = map fst pics
322− let txt = if (null alternate) || (alternate == [Str ""]) ||
314+ let txt = if null alternate || alternate == [Str ""] ||
323− (alternate `elem` labelsUsed)
315+ alternate `elem` labelsUsed
324 then [Str $ "image" ++ show (length refs)]
316 then [Str $ "image" ++ show (length refs)]
325 else alternate
317 else alternate
326 let pics' = if (txt, (source, tit)) `elem` pics
318 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
27
27
28Conversion of 'Pandoc' documents to RTF (rich text format).
28Conversion of 'Pandoc' documents to RTF (rich text format).
29-}
29-}
30−module Text.Pandoc.Writers.RTF ( writeRTF) where
30+module Text.Pandoc.Writers.RTF ( writeRTF ) where
31import Text.Pandoc.Definition
31import Text.Pandoc.Definition
32import Text.Pandoc.Shared
32import Text.Pandoc.Shared
33import Text.Regex ( matchRegexAll, mkRegex )
33import Text.Regex ( matchRegexAll, mkRegex )
34import Data.List ( isSuffixOf )
34import Data.List ( isSuffixOf )
35−import Data.Char ( ord, chr )
35+import Data.Char ( ord )
36
36
37-- | Convert Pandoc to a string in rich text format.
37-- | Convert Pandoc to a string in rich text format.
38writeRTF :: WriterOptions -> Pandoc -> String
38writeRTF :: WriterOptions -> Pandoc -> String
@@ -44,22 +44,22 @@ writeRTF options (Pandoc meta blocks) =
44 then tableOfContents $ filter isHeaderBlock blocks
44 then tableOfContents $ filter isHeaderBlock blocks
45 else ""
45 else ""
46 foot = if writerStandalone options then "\n}\n" else ""
46 foot = if writerStandalone options then "\n}\n" else ""
47− body = (writerIncludeBefore options) ++
47+ body = writerIncludeBefore options ++
48 concatMap (blockToRTF 0 AlignDefault) blocks ++
48 concatMap (blockToRTF 0 AlignDefault) blocks ++
49− (writerIncludeAfter options) in
49+ writerIncludeAfter options
50− head ++ toc ++ body ++ foot
50+ in head ++ toc ++ body ++ foot
51
51
52-- | Construct table of contents from list of header blocks.
52-- | Construct table of contents from list of header blocks.
53tableOfContents :: [Block] -> String
53tableOfContents :: [Block] -> String
54tableOfContents headers =
54tableOfContents headers =
55 let contentsTree = hierarchicalize headers
55 let contentsTree = hierarchicalize headers
56− in concatMap (blockToRTF 0 AlignDefault) $ [Header 1 [Str "Contents"],
56+ in concatMap (blockToRTF 0 AlignDefault) $
57− BulletList (map elementToListItem contentsTree)]
57+ [Header 1 [Str "Contents"],
58+ BulletList (map elementToListItem contentsTree)]
58
59
59elementToListItem :: Element -> [Block]
60elementToListItem :: Element -> [Block]
60elementToListItem (Blk _) = []
61elementToListItem (Blk _) = []
61−elementToListItem (Sec sectext subsecs) =
62+elementToListItem (Sec sectext subsecs) = [Plain sectext] ++
62− [Plain sectext] ++
63 if null subsecs
63 if null subsecs
64 then []
64 then []
65 else [BulletList (map elementToListItem subsecs)]
65 else [BulletList (map elementToListItem subsecs)]
@@ -67,10 +67,10 @@ elementToListItem (Sec sectext subsecs) =
67-- | Convert unicode characters (> 127) into rich text format representation.
67-- | Convert unicode characters (> 127) into rich text format representation.
68handleUnicode :: String -> String
68handleUnicode :: String -> String
69handleUnicode [] = []
69handleUnicode [] = []
70−handleUnicode (c:cs) = if (ord c) > 127
70+handleUnicode (c:cs) =
71− then '\\':'u':(show (ord c)) ++ "?" ++
71+ if ord c > 127
72− (handleUnicode cs)
72+ then '\\':'u':(show (ord c)) ++ "?" ++ handleUnicode cs
73− else c:(handleUnicode cs)
73+ else c:(handleUnicode cs)
74
74
75-- | Escape special characters.
75-- | Escape special characters.
76escapeSpecial :: String -> String
76escapeSpecial :: String -> String
@@ -127,7 +127,7 @@ listIncrement = 360
127
127
128-- | Returns appropriate bullet list marker for indent level.
128-- | Returns appropriate bullet list marker for indent level.
129bulletMarker :: Int -> String
129bulletMarker :: Int -> String
130−bulletMarker indent = case (indent `mod` 720) of
130+bulletMarker indent = case indent `mod` 720 of
131 0 -> "\\bullet "
131 0 -> "\\bullet "
132 otherwise -> "\\endash "
132 otherwise -> "\\endash "
133
133
@@ -135,7 +135,7 @@ bulletMarker indent = case (indent `mod` 720) of
135orderedMarkers :: Int -> ListAttributes -> [String]
135orderedMarkers :: Int -> ListAttributes -> [String]
136orderedMarkers indent (start, style, delim) =
136orderedMarkers indent (start, style, delim) =
137 if style == DefaultStyle && delim == DefaultDelim
137 if style == DefaultStyle && delim == DefaultDelim
138− then case (indent `mod` 720) of
138+ then case indent `mod` 720 of
139 0 -> orderedListMarkers (start, Decimal, Period)
139 0 -> orderedListMarkers (start, Decimal, Period)
140 otherwise -> orderedListMarkers (start, LowerAlpha, Period)
140 otherwise -> orderedListMarkers (start, LowerAlpha, Period)
141 else orderedListMarkers (start, style, delim)
141 else orderedListMarkers (start, style, delim)
@@ -145,21 +145,21 @@ rtfHeader :: String -- ^ header text
145 -> Meta -- ^ bibliographic information
145 -> Meta -- ^ bibliographic information
146 -> String
146 -> String
147rtfHeader headerText (Meta title authors date) =
147rtfHeader 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
149 then ""
153 then ""
150− else rtfPar 0 0 AlignCenter ("\\b \\fs36 " ++
154+ else rtfPar 0 0 AlignCenter (" " ++ (joinWithSep "\\" $
151− inlineListToRTF title)
155+ map stringToRTF authors))
152− authorstext = if null authors
156+ datetext = if date == ""
153− then ""
154− else rtfPar 0 0 AlignCenter (" " ++ (joinWithSep "\\"
155− (map stringToRTF authors)))
156− datetext = if date == ""
157− then ""
158− else rtfPar 0 0 AlignCenter (" " ++ stringToRTF date) in
159− let spacer = if null (titletext ++ authorstext ++ datetext)
160 then ""
157 then ""
161− else rtfPar 0 0 AlignDefault "" in
158+ else rtfPar 0 0 AlignCenter (" " ++ stringToRTF date) in
162− headerText ++ titletext ++ authorstext ++ datetext ++ spacer
159+ let spacer = if null (titletext ++ authorstext ++ datetext)
160+ then ""
161+ else rtfPar 0 0 AlignDefault "" in
162+ headerText ++ titletext ++ authorstext ++ datetext ++ spacer
163
163
164-- | Convert Pandoc block element to RTF.
164-- | Convert Pandoc block element to RTF.
165blockToRTF :: Int -- ^ indent level
165blockToRTF :: Int -- ^ indent level
@@ -168,31 +168,27 @@ blockToRTF :: Int -- ^ indent level
168 -> String
168 -> String
169blockToRTF _ _ Null = ""
169blockToRTF _ _ Null = ""
170blockToRTF indent alignment (Plain lst) =
170blockToRTF indent alignment (Plain lst) =
171− rtfCompact indent 0 alignment (inlineListToRTF lst)
171+ rtfCompact indent 0 alignment $ inlineListToRTF lst
172blockToRTF indent alignment (Para lst) =
172blockToRTF indent alignment (Para lst) =
173− rtfPar indent 0 alignment (inlineListToRTF lst)
173+ rtfPar indent 0 alignment $ inlineListToRTF lst
174blockToRTF indent alignment (BlockQuote lst) =
174blockToRTF indent alignment (BlockQuote lst) =
175 concatMap (blockToRTF (indent + indentIncrement) alignment) lst
175 concatMap (blockToRTF (indent + indentIncrement) alignment) lst
176blockToRTF indent _ (CodeBlock str) =
176blockToRTF indent _ (CodeBlock str) =
177 rtfPar indent 0 AlignLeft ("\\f1 " ++ (codeStringToRTF str))
177 rtfPar indent 0 AlignLeft ("\\f1 " ++ (codeStringToRTF str))
178blockToRTF _ _ (RawHtml str) = ""
178blockToRTF _ _ (RawHtml str) = ""
179−blockToRTF indent alignment (BulletList lst) =
179+blockToRTF indent alignment (BulletList lst) = spaceAtEnd $
180− spaceAtEnd $
181 concatMap (listItemToRTF alignment indent (bulletMarker indent)) lst
180 concatMap (listItemToRTF alignment indent (bulletMarker indent)) lst
182−blockToRTF indent alignment (OrderedList attribs lst) =
181+blockToRTF indent alignment (OrderedList attribs lst) = spaceAtEnd $ concat $
183− spaceAtEnd $ concat $
184 zipWith (listItemToRTF alignment indent) (orderedMarkers indent attribs) lst
182 zipWith (listItemToRTF alignment indent) (orderedMarkers indent attribs) lst
185−blockToRTF indent alignment (DefinitionList lst) =
183+blockToRTF indent alignment (DefinitionList lst) = spaceAtEnd $
186− spaceAtEnd $
187 concatMap (definitionListItemToRTF alignment indent) lst
184 concatMap (definitionListItemToRTF alignment indent) lst
188blockToRTF indent _ HorizontalRule =
185blockToRTF indent _ HorizontalRule =
189 rtfPar indent 0 AlignCenter "\\emdash\\emdash\\emdash\\emdash\\emdash"
186 rtfPar indent 0 AlignCenter "\\emdash\\emdash\\emdash\\emdash\\emdash"
190−blockToRTF indent alignment (Header level lst) =
187+blockToRTF indent alignment (Header level lst) = rtfPar indent 0 alignment $
191− rtfPar indent 0 alignment ("\\b \\fs" ++ (show (40 - (level * 4))) ++ " " ++
188+ "\\b \\fs" ++ (show (40 - (level * 4))) ++ " " ++ inlineListToRTF lst
192− (inlineListToRTF lst))
193blockToRTF indent alignment (Table caption aligns sizes headers rows) =
189blockToRTF indent alignment (Table caption aligns sizes headers rows) =
194− (tableRowToRTF True indent aligns sizes headers) ++ (concatMap
190+ tableRowToRTF True indent aligns sizes headers ++
195− (tableRowToRTF False indent aligns sizes) rows) ++
191+ concatMap (tableRowToRTF False indent aligns sizes) rows ++
196 rtfPar indent 0 alignment (inlineListToRTF caption)
192 rtfPar indent 0 alignment (inlineListToRTF caption)
197
193
198tableRowToRTF :: Bool -> Int -> [Alignment] -> [Float] -> [[Block]] -> String
194tableRowToRTF :: Bool -> Int -> [Alignment] -> [Float] -> [[Block]] -> String
@@ -201,8 +197,10 @@ tableRowToRTF header indent aligns sizes cols =
201 totalTwips = 6 * 1440 -- 6 inches
197 totalTwips = 6 * 1440 -- 6 inches
202 rightEdges = tail $ scanl (\sofar new -> sofar + floor (new * totalTwips))
198 rightEdges = tail $ scanl (\sofar new -> sofar + floor (new * totalTwips))
203 0 sizes
199 0 sizes
204− cellDefs = map (\edge -> (if header then "\\clbrdrb\\brdrs"
200+ cellDefs = map (\edge -> (if header
205− else "") ++ "\\cellx" ++ show edge) rightEdges
201+ then "\\clbrdrb\\brdrs"
202+ else "") ++ "\\cellx" ++ show edge)
203+ rightEdges
206 start = "{\n\\trowd \\trgaph120\n" ++ concat cellDefs ++ "\n" ++
204 start = "{\n\\trowd \\trgaph120\n" ++ concat cellDefs ++ "\n" ++
207 "\\trkeep\\intbl\n{\n"
205 "\\trkeep\\intbl\n{\n"
208 end = "}\n\\intbl\\row}\n"
206 end = "}\n\\intbl\\row}\n"
@@ -234,11 +232,12 @@ listItemToRTF alignment indent marker list =
234 let (first:rest) = map (blockToRTF (indent + listIncrement) alignment) list in
232 let (first:rest) = map (blockToRTF (indent + listIncrement) alignment) list in
235 -- insert the list marker into the (processed) first block
233 -- insert the list marker into the (processed) first block
236 let modFirst = case matchRegexAll (mkRegex "\\\\fi-?[0-9]+") first of
234 let modFirst = case matchRegexAll (mkRegex "\\\\fi-?[0-9]+") first of
237− Just (before, matched, after, _) -> before ++ "\\fi" ++
235+ Just (before, matched, after, _) ->
238− show (0 - listIncrement) ++ " " ++ marker ++ "\\tx" ++
236+ before ++ "\\fi" ++ show (0 - listIncrement) ++
239− show listIncrement ++ "\\tab" ++ after
237+ " " ++ marker ++ "\\tx" ++
238+ show listIncrement ++ "\\tab" ++ after
240 Nothing -> first in
239 Nothing -> first in
241− modFirst ++ (concat rest)
240+ modFirst ++ concat rest
242
241
243-- | Convert definition list item (label, list of blocks) to RTF.
242-- | Convert definition list item (label, list of blocks) to RTF.
244definitionListItemToRTF :: Alignment -- ^ alignment
243definitionListItemToRTF :: Alignment -- ^ alignment
@@ -285,4 +284,3 @@ inlineToRTF (Image alternate (source, tit)) =
285inlineToRTF (Note contents) =
284inlineToRTF (Note contents) =
286 "{\\super\\chftn}{\\*\\footnote\\chftn\\~\\plain\\pard " ++
285 "{\\super\\chftn}{\\*\\footnote\\chftn\\~\\plain\\pard " ++
287 (concatMap (blockToRTF 0 AlignDefault) contents) ++ "}"
286 (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+-}
2module Text.Pandoc.Writers.DefaultHeaders (
30module Text.Pandoc.Writers.DefaultHeaders (
3 defaultLaTeXHeader,
31 defaultLaTeXHeader,
4 defaultConTeXtHeader,
32 defaultConTeXtHeader,
src/templates/S5.hs+47−12
@@ -1,5 +1,33 @@
1−-- | Definitions for creation of S5 powerpoint-like HTML.
1+{-
2−-- (See <http://meyerweb.com/eric/tools/s5/>.)
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+-}
3module Text.Pandoc.Writers.S5 (
31module Text.Pandoc.Writers.S5 (
4 -- * Strings
32 -- * Strings
5 s5Meta,
33 s5Meta,
@@ -60,13 +88,13 @@ layoutDiv :: [Inline] -- ^ Title of document (for header or footer)
60 -> [Block] -- ^ List of block elements returned
88 -> [Block] -- ^ List of block elements returned
61layoutDiv 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")]
89layoutDiv 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")]
62
90
63−presentationStart = (RawHtml "<div class=\"presentation\">\n\n")
91+presentationStart = RawHtml "<div class=\"presentation\">\n\n"
64
92
65−presentationEnd = (RawHtml "</div>\n")
93+presentationEnd = RawHtml "</div>\n"
66
94
67−slideStart = (RawHtml "<div class=\"slide\">\n")
95+slideStart = RawHtml "<div class=\"slide\">\n"
68
96
69−slideEnd = (RawHtml "</div>\n")
97+slideEnd = RawHtml "</div>\n"
70
98
71-- | Returns 'True' if block is a Header 1.
99-- | Returns 'True' if block is a Header 1.
72isH1 :: Block -> Bool
100isH1 :: Block -> Bool
@@ -84,15 +112,22 @@ insertSlides beginning blocks =
84 beforeHead ++ [slideEnd]
112 beforeHead ++ [slideEnd]
85 else
113 else
86 if beginning then
114 if beginning then
87− beforeHead ++ slideStart:(head rest):(insertSlides False (tail rest))
115+ beforeHead ++
116+ slideStart:(head rest):(insertSlides False (tail rest))
88 else
117 else
89− beforeHead ++ slideEnd:slideStart:(head rest):(insertSlides False (tail rest))
118+ beforeHead ++
119+ slideEnd:slideStart:(head rest):(insertSlides False (tail rest))
90
120
91-- | Insert blocks into 'Pandoc' for slide structure.
121-- | Insert blocks into 'Pandoc' for slide structure.
92insertS5Structure :: Pandoc -> Pandoc
122insertS5Structure :: Pandoc -> Pandoc
93insertS5Structure (Pandoc meta []) = Pandoc meta []
123insertS5Structure (Pandoc meta []) = Pandoc meta []
94insertS5Structure (Pandoc (Meta title authors date) blocks) =
124insertS5Structure (Pandoc (Meta title authors date) blocks) =
95− let slides = insertSlides True blocks
125+ 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
126+ firstSlide = if not (null title)
97− let newBlocks = (layoutDiv title date) ++ presentationStart:firstSlide ++ slides ++ [presentationEnd] in
127+ then [slideStart, (Header 1 title),
98− Pandoc (Meta title authors date) newBlocks
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