@@ -29,20 +29,21 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29Conversion from reStructuredText to 'Pandoc' document.
29Conversion from reStructuredText to 'Pandoc' document.
31module Text.Pandoc.Readers.RST (
31module Text.Pandoc.Readers.RST (
34import Text.Pandoc.Definition
35import Text.Pandoc.Definition
35import Text.Pandoc.Builder (setMeta, fromList)
36import Text.Pandoc.Builder (setMeta, fromList)
36import Text.Pandoc.Shared
37import Text.Pandoc.Shared
37import Text.Pandoc.Parsing
38import Text.Pandoc.Parsing
38import Text.Pandoc.Options
39import Text.Pandoc.Options
39−import Control.Monad ( when, liftM, guard, mzero, mplus )
40+import Control.Monad ( when, liftM, guard, mzero )
40import Data.List ( findIndex, intersperse, intercalate,
41import Data.List ( findIndex, intersperse, intercalate,
41− transpose, sort, deleteFirstsBy, isSuffixOf )
42+ transpose, sort, deleteFirstsBy, isSuffixOf , nub, union)
42import Data.Maybe (fromMaybe)
43import Data.Maybe (fromMaybe)
43import qualified Data.Map as M
44import qualified Data.Map as M
44import Text.Printf ( printf )
45import Text.Printf ( printf )
45−import Control.Applicative ((<$>), (<$), (<*), (*>), (<*>))
46+import Control.Applicative ((<$>), (<$), (<*), (*>), (<*>), pure)
46import Text.Pandoc.Builder (Inlines, Blocks, trimInlines, (<>))
47import Text.Pandoc.Builder (Inlines, Blocks, trimInlines, (<>))
47import qualified Text.Pandoc.Builder as B
48import qualified Text.Pandoc.Builder as B
48import Data.Monoid (mconcat, mempty)
49import Data.Monoid (mconcat, mempty)
@@ -55,6 +56,9 @@ readRST :: ReaderOptions -- ^ Reader options
56readRST opts s = (readWith parseRST) def{ stateOptions = opts } (s ++ "\n\n")
57readRST opts s = (readWith parseRST) def{ stateOptions = opts } (s ++ "\n\n")
59+readRSTWithWarnings :: ReaderOptions -> String -> (Pandoc, [String])
60+readRSTWithWarnings opts s = (readWithWarnings parseRST) def{ stateOptions = opts } (s ++ "\n\n")
58type RSTParser = Parser [Char] ParserState
62type RSTParser = Parser [Char] ParserState
@@ -608,38 +612,62 @@ directive' = do
608 "" -> block
612 "" -> block
609 _ -> parseFromString parseBlocks body'
613 _ -> parseFromString parseBlocks body'
610 return $ B.divWith attrs children
614 return $ B.divWith attrs children
617+ addWarning (Just pos) $ "ignoring unknown directive: " ++ other
611− _ -> return mempty
618+ return mempty
614-- - Silently ignores illegal fields
621-- - Silently ignores illegal fields
615−-- - Silently drops classes
616-- - Only supports :format: fields with a single format for :raw: roles,
622-- - Only supports :format: fields with a single format for :raw: roles,
617-- change Text.Pandoc.Definition.Format to fix
623-- change Text.Pandoc.Definition.Format to fix
618addNewRole :: String -> [(String, String)] -> RSTParser Blocks
624addNewRole :: String -> [(String, String)] -> RSTParser Blocks
619addNewRole roleString fields = do
625addNewRole roleString fields = do
620 (role, parentRole) <- parseFromString inheritedRole roleString
626 (role, parentRole) <- parseFromString inheritedRole roleString
621 customRoles <- stateRstCustomRoles <$> getState
627 customRoles <- stateRstCustomRoles <$> getState
628+ let (baseRole, baseFmt, baseAttr) =
629+ maybe (parentRole, Nothing, nullAttr) id $
622− baseRole <- case M.lookup parentRole customRoles of
630+ M.lookup parentRole customRoles
623− Just (base, _, _) -> return base
624− Nothing -> return parentRole
626− let fmt = if baseRole == "raw" then lookup "format" fields else Nothing
631+ fmt = if parentRole == "raw" then lookup "format" fields else baseFmt
627− annotate = maybe id addLanguage $
632+ annotate :: [String] -> [String]
633+ annotate = maybe id (:) $
628− if baseRole == "code"
634+ if parentRole == "code"
629 then lookup "language" fields
635 then lookup "language" fields
630 else Nothing
636 else Nothing
637+ attr = let (ident, classes, keyValues) = baseAttr
638+ -- nub in case role name & language class are the same
639+ in (ident, nub . (role :) . annotate $ classes, keyValues)
641+ -- warn about syntax we ignore
642+ flip mapM_ fields $ \(key, _) -> case key of
643+ "language" -> when (parentRole /= "code") $ addWarning Nothing $
644+ "ignoring :language: field because the parent of role :" ++
645+ role ++ ": is :" ++ parentRole ++ ": not :code:"
646+ "format" -> when (parentRole /= "raw") $ addWarning Nothing $
647+ "ignoring :format: field because the parent of role :" ++
648+ role ++ ": is :" ++ parentRole ++ ": not :raw:"
649+ _ -> addWarning Nothing $ "ignoring unknown field :" ++ key ++
650+ ": in definition of role :" ++ role ++ ": in"
651+ when (parentRole == "raw" && countKeys "format" > 1) $
652+ addWarning Nothing $
653+ "ignoring :format: fields after the first in the definition of role :"
655+ when (parentRole == "code" && countKeys "language" > 1) $
656+ addWarning Nothing $
657+ "ignoring :language: fields after the first in the definition of role :"
632 updateState $ \s -> s {
660 updateState $ \s -> s {
633 stateRstCustomRoles =
661 stateRstCustomRoles =
634− M.insert role (baseRole, fmt, (,) parentRole . annotate) customRoles
662+ M.insert role (baseRole, fmt, attr) customRoles
637 return $ B.singleton Null
665 return $ B.singleton Null
639− addLanguage lang (ident, classes, keyValues) =
667+ countKeys k = length . filter (== k) . map fst $ fields
640− (ident, "sourceCode" : lang : classes, keyValues)
641 inheritedRole =
668 inheritedRole =
642− (,) <$> roleNameEndingIn (char '(') <*> roleNameEndingIn (char ')')
669+ (,) <$> roleName <*> ((char '(' *> roleName <* char ')') <|> pure "span")
644-- Can contain character codes as decimal numbers or
672-- Can contain character codes as decimal numbers or
645-- hexadecimal numbers, prefixed by 0x, x, \x, U+, u, or \u
673-- hexadecimal numbers, prefixed by 0x, x, \x, U+, u, or \u
@@ -999,21 +1027,23 @@ renderRole contents fmt role attr = case role of
999 "RFC" -> return $ rfcLink contents
1027 "RFC" -> return $ rfcLink contents
1000 "pep-reference" -> return $ pepLink contents
1028 "pep-reference" -> return $ pepLink contents
1001 "PEP" -> return $ pepLink contents
1029 "PEP" -> return $ pepLink contents
1002− "literal" -> return $ B.str contents
1030+ "literal" -> return $ B.codeWith attr contents
1003 "math" -> return $ B.math contents
1031 "math" -> return $ B.math contents
1004 "title-reference" -> titleRef contents
1032 "title-reference" -> titleRef contents
1005 "title" -> titleRef contents
1033 "title" -> titleRef contents
1006 "t" -> titleRef contents
1034 "t" -> titleRef contents
1007− "code" -> return $ B.codeWith attr contents
1035+ "code" -> return $ B.codeWith (addClass "sourceCode" attr) contents
1036+ "span" -> return $ B.spanWith attr $ B.str contents
1008 "raw" -> return $ B.rawInline (fromMaybe "" fmt) contents
1037 "raw" -> return $ B.rawInline (fromMaybe "" fmt) contents
1009 custom -> do
1038 custom -> do
1010− customRole <- stateRstCustomRoles <$> getState
1039+ customRoles <- stateRstCustomRoles <$> getState
1011− case M.lookup custom customRole of
1040+ case M.lookup custom customRoles of
1012− Just (_, newFmt, inherit) -> let
1041+ Just (newRole, newFmt, newAttr) ->
1013− fmtStr = fmt `mplus` newFmt
1014− (newRole, newAttr) = inherit attr
1015− in renderRole contents fmtStr newRole newAttr
1042+ renderRole contents newFmt newRole newAttr
1045+ addWarning (Just pos) $ "ignoring unknown role :" ++ custom ++ ": in"
1016− Nothing -> return $ B.str contents -- Undefined role
1046+ return $ B.str contents -- Undefined role
1018 titleRef ref = return $ B.str ref -- FIXME: Not a sensible behaviour
1048 titleRef ref = return $ B.str ref -- FIXME: Not a sensible behaviour
1019 rfcLink rfcNo = B.link rfcUrl ("RFC " ++ rfcNo) $ B.str ("RFC " ++ rfcNo)
1049 rfcLink rfcNo = B.link rfcUrl ("RFC " ++ rfcNo) $ B.str ("RFC " ++ rfcNo)
@@ -1022,11 +1052,14 @@ renderRole contents fmt role attr = case role of
1022 where padNo = replicate (4 - length pepNo) '0' ++ pepNo
1052 where padNo = replicate (4 - length pepNo) '0' ++ pepNo
1023 pepUrl = "http://www.python.org/dev/peps/pep-" ++ padNo ++ "/"
1053 pepUrl = "http://www.python.org/dev/peps/pep-" ++ padNo ++ "/"
1055+addClass :: String -> Attr -> Attr
1056+addClass c (ident, classes, keyValues) = (ident, union classes [c], keyValues)
1025−roleNameEndingIn :: RSTParser Char -> RSTParser String
1058+roleName :: RSTParser String
1026−roleNameEndingIn end = many1Till (letter <|> char '-') end
1059+roleName = many1 (letter <|> char '-')
1028roleMarker :: RSTParser String
1061roleMarker :: RSTParser String
1029−roleMarker = char ':' *> roleNameEndingIn (char ':')
1062+roleMarker = char ':' *> roleName <* char ':'
1031roleBefore :: RSTParser (String,String)
1064roleBefore :: RSTParser (String,String)
1032roleBefore = try $ do
1065roleBefore = try $ do