@@ -40,6 +40,7 @@ import Control.Monad
40import Control.Monad.Except (throwError)
40import Control.Monad.Except (throwError)
41import Data.Char (chr, isAlphaNum, isLetter, ord)
41import Data.Char (chr, isAlphaNum, isLetter, ord)
42import Data.Text (Text, unpack)
42import Data.Text (Text, unpack)
43+import qualified Data.Text as T
43import Data.List (intercalate, isPrefixOf)
44import Data.List (intercalate, isPrefixOf)
44import qualified Data.Map as M
45import qualified Data.Map as M
45import Data.Maybe (fromMaybe, maybeToList)
46import Data.Maybe (fromMaybe, maybeToList)
@@ -47,16 +48,373 @@ import Safe (minimumDef)
47import System.FilePath (addExtension, replaceExtension, takeExtension)
48import System.FilePath (addExtension, replaceExtension, takeExtension)
48import Text.Pandoc.Builder
49import Text.Pandoc.Builder
49import Text.Pandoc.Class (PandocMonad, PandocPure, lookupEnv, readFileFromDirs,
50import Text.Pandoc.Class (PandocMonad, PandocPure, lookupEnv, readFileFromDirs,
50− report, setResourcePath, getResourcePath)
51+ report, setResourcePath, getResourcePath,
52+ runIOorExplode, PandocIO)
51import Text.Pandoc.Highlighting (fromListingsLanguage, languagesByExtension)
53import Text.Pandoc.Highlighting (fromListingsLanguage, languagesByExtension)
52import Text.Pandoc.ImageSize (numUnit, showFl)
54import Text.Pandoc.ImageSize (numUnit, showFl)
53import Text.Pandoc.Logging
55import Text.Pandoc.Logging
54import Text.Pandoc.Options
56import Text.Pandoc.Options
55import Text.Pandoc.Parsing hiding (many, mathDisplay, mathInline, optional,
57import Text.Pandoc.Parsing hiding (many, mathDisplay, mathInline, optional,
56− space, (<|>))
58+ space, (<|>), spaces, blankline)
57import Text.Pandoc.Shared
59import Text.Pandoc.Shared
58import Text.Pandoc.Walk
60import Text.Pandoc.Walk
62+import Text.Pandoc.Extensions (getDefaultExtensions)
64+-- | Parse LaTeX from string and return 'Pandoc' document.
65+readLaTeX :: PandocMonad m
66+ => ReaderOptions -- ^ Reader options
67+ -> Text -- ^ String to parse (assumes @'\n'@ line endings)
69+readLaTeX opts ltx = undefined
71+testParser :: LP PandocIO a -> Text -> IO a
73+ res <- runIOorExplode (runParserT p defaultParserState{
74+ stateOptions = def{ readerExtensions =
75+ getDefaultExtensions "latex" }} "name" (tokenize t))
77+ Left e -> error (show e)
80+type LP m = ParserT [Tok] ParserState m
82+rawLaTeXBlock :: PandocMonad m => LP m String
85+rawLaTeXInline :: PandocMonad m => LP m Inline
86+rawLaTeXInline = mzero
88+inlineCommand :: PandocMonad m => LP m Inlines
91+data TokType = CtrlSeq | Spaces | Newline | Symbol | Word | Comment |
93+ deriving (Eq, Ord, Show)
95+data Tok = Tok (Line, Column) TokType Text
96+ deriving (Eq, Ord, Show)
98+tokenize :: Text -> [Tok]
99+tokenize = totoks (1, 1)
101+totoks :: (Line, Column) -> Text -> [Tok]
107+ Tok (lin, col) Newline (T.singleton '\n')
108+ : totoks (lin + 1,1) rest
110+ let (sps, rest') = T.span isSpaceOrTab t
111+ in Tok (lin, col) Spaces sps
112+ : totoks (lin, col + T.length sps) rest'
114+ let (ws, rest') = T.span isAlphaNum t
115+ in Tok (lin, col) Word ws
116+ : totoks (lin, col + T.length ws) rest'
118+ let (cs, rest') = T.break (== '\n') rest
119+ in Tok (lin, col) Comment (T.singleton '%' <> cs)
120+ : totoks (lin, col + 1 + T.length cs) rest'
122+ case T.uncons rest of
123+ Nothing -> [Tok (lin, col) Symbol (T.singleton c)]
126+ let (ws, rest'') = T.span isLetter rest
127+ in Tok (lin, col) CtrlSeq (T.singleton '\\' <> ws)
128+ : totoks (lin, col + 1 + T.length ws) rest''
129+ | d == '\t' || d == '\n' ->
130+ Tok (lin, col) Symbol (T.singleton '\\')
131+ : totoks (lin, col + 1) rest
133+ Tok (lin, col) CtrlSeq (T.pack [c,d])
134+ : totoks (lin, col + 2) rest'
136+ case T.uncons rest of
137+ Just ('^', rest') ->
138+ case T.uncons rest' of
141+ case T.uncons rest'' of
142+ Just (e, rest''') | isLowerHex e ->
143+ Tok (lin, col) Esc2 (T.pack ['^','^',d,e])
144+ : totoks (lin, col + 4) rest'''
146+ Tok (lin, col) Esc1 (T.pack ['^','^',d])
147+ : totoks (lin, col + 3) rest''
149+ Tok (lin, col) Esc1 (T.pack ['^','^',d])
150+ : totoks (lin, col + 3) rest''
151+ _ -> [Tok (lin, col) Symbol (T.singleton '^'),
152+ Tok (lin, col + 1) Symbol (T.singleton '^')]
153+ Nothing -> [Tok (lin, col) Symbol (T.singleton '^')]
155+ Tok (lin, col) Symbol (T.singleton c) : totoks (lin, col + 1) rest
157+ where isSpaceOrTab ' ' = True
158+ isSpaceOrTab '\t' = True
159+ isSpaceOrTab _ = False
161+isLowerHex :: Char -> Bool
162+isLowerHex x = x >= '0' && x <= '9' || x >= 'a' && x <= 'f'
164+untokenize :: [Tok] -> Text
165+untokenize = mconcat . map untoken
167+untoken :: Tok -> Text
168+untoken (Tok _ _ t) = t
170+satisfyTok :: Monad m
172+ -> ParserT [Tok] u m Tok
173+satisfyTok f = tokenPrim (T.unpack . untoken) updatePos matcher
174+ where matcher t | f t = Just t
175+ | otherwise = Nothing
176+ updatePos :: SourcePos -> Tok -> [Tok] -> SourcePos
177+ updatePos spos _ (Tok (lin,col) _ _ : _) =
178+ setSourceColumn (setSourceLine spos lin) col
179+ updatePos spos _ [] = spos
181+anyControlSeq :: PandocMonad m => LP m Tok
182+anyControlSeq = satisfyTok isCtrlSeq <* spaces
183+ where isCtrlSeq (Tok _ CtrlSeq _) = True
186+spaces :: PandocMonad m => LP m ()
187+spaces = skipMany (satisfyTok (tokTypeIn [Comment, Spaces, Newline]))
189+spaces1 :: PandocMonad m => LP m ()
190+spaces1 = skipMany1 (satisfyTok (tokTypeIn [Comment, Spaces, Newline]))
192+tokTypeIn :: [TokType] -> Tok -> Bool
193+tokTypeIn toktypes (Tok _ tt _) = tt `elem` toktypes
194+tokTypeIn _ _ = False
196+controlSeq :: PandocMonad m => Text -> LP m Tok
197+controlSeq name = satisfyTok isNamed
198+ where isNamed t@(Tok _ CtrlSeq n) = n == T.singleton '\\' <> name
201+symbol :: PandocMonad m => Char -> LP m Tok
202+symbol c = satisfyTok isc
203+ where isc (Tok _ Symbol d) = case T.uncons d of
204+ Just (c',_) -> c == c'
208+symbolIn :: PandocMonad m => [Char] -> LP m Tok
209+symbolIn cs = satisfyTok isInCs
210+ where isInCs (Tok _ Symbol d) = case T.uncons d of
211+ Just (c,_) -> c `elem` cs
215+sp :: PandocMonad m => LP m ()
216+sp = whitespace <|> endline
218+whitespace :: PandocMonad m => LP m ()
219+whitespace = () <$ satisfyTok isSpaceTok
220+ where isSpaceTok (Tok _ Spaces _) = True
221+ isSpaceTok _ = False
223+newlineTok :: PandocMonad m => LP m ()
224+newlineTok = () <$ satisfyTok isNewlineTok
226+isNewlineTok :: Tok -> Bool
227+isNewlineTok (Tok _ Newline _) = True
228+isNewlineTok _ = False
230+comment :: PandocMonad m => LP m ()
231+comment = () <$ satisfyTok isCommentTok
232+ where isCommentTok (Tok _ Comment _) = True
233+ isCommentTok _ = False
235+anyTok :: PandocMonad m => LP m Tok
236+anyTok = satisfyTok (const True)
238+endline :: PandocMonad m => LP m ()
242+ notFollowedBy blankline
244+blankline :: PandocMonad m => LP m ()
246+ skipMany (satisfyTok (tokTypeIn [Spaces, Newline]))
249+primEscape :: PandocMonad m => LP m Char
251+ Tok _ toktype t <- satisfyTok (tokTypeIn [Esc1, Esc2])
253+ Esc1 -> case T.uncons t of
255+ | c >= '\64' && c <= '\127' -> return (chr (ord c - 64))
256+ | otherwise -> return (chr (ord c + 64))
257+ Nothing -> fail "Empty content of Esc1"
258+ Esc2 -> case safeRead ('0':'x':T.unpack (T.drop 2 t)) of
259+ Just x -> return (chr x)
260+ Nothing -> fail $ "Could not read: " ++ T.unpack t
261+ _ -> fail "Expected an Esc1 or Esc2 token" -- should not happen
263+bgroup :: PandocMonad m => LP m ()
266+ symbol '{' <|> controlSeq "bgroup" <|> controlSeq "begingroup"
269+egroup :: PandocMonad m => LP m ()
270+egroup = () <$ (symbol '}' <|> controlSeq "egroup" <|> controlSeq "endgroup")
272+grouped :: (PandocMonad m, Monoid a) => LP m a -> LP m a
273+grouped parser = try $ do
275+ -- first we check for an inner 'grouped', because
276+ -- {{a,b}} should be parsed the same as {a,b}
277+ try (grouped parser <* egroup) <|> (mconcat <$> manyTill parser egroup)
279+braced :: PandocMonad m => LP m [Tok]
282+ -- {{a,b}} should be parsed the same as {a,b}
283+ try (braced <* egroup) <|> manyTill anyTok egroup
285+bracketed :: PandocMonad m => Monoid a => LP m a -> LP m a
286+bracketed parser = try $ do
288+ mconcat <$> manyTill parser (symbol ']')
292+inlineText :: PandocMonad m => LP m Inlines
293+inlineText = (str . T.unpack . untoken) <$> satisfyTok isWordTok
294+ where isWordTok (Tok _ Word _) = True
297+inlineGroup :: PandocMonad m => LP m Inlines
299+ ils <- grouped inline
302+ else return $ spanWith nullAttr ils
303+ -- we need the span so we can detitlecase bibtex entries;
304+ -- we need to know when something is {C}apitalized
306+doLHSverb :: PandocMonad m => LP m Inlines
308+ (codeWith ("",["haskell"],[]) . T.unpack . untokenize)
309+ <$> manyTill (satisfyTok (not . isNewlineTok)) (symbol '|')
311+lit :: String -> LP m Inlines
314+doubleQuote :: PandocMonad m => LP m Inlines
316+ quoted' doubleQuoted (try $ count 2 $ symbol '`')
317+ (void $ try $ count 2 $ symbol '\'')
318+ <|> quoted' doubleQuoted ((:[]) <$> symbol '“') (void $ symbol '”')
319+ -- the following is used by babel for localized quotes:
320+ <|> quoted' doubleQuoted (try $ sequence [symbol '"', symbol '`'])
321+ (void $ try $ sequence [symbol '"', symbol '\''])
322+ <|> quoted' doubleQuoted ((:[]) <$> symbol '"')
325+singleQuote :: PandocMonad m => LP m Inlines
327+ quoted' singleQuoted ((:[]) <$> symbol '`')
328+ (try $ symbol '\'' >>
329+ notFollowedBy (satisfyTok startsWithLetter))
330+ <|> quoted' singleQuoted ((:[]) <$> symbol '‘')
331+ (try $ symbol '’' >>
332+ notFollowedBy (satisfyTok startsWithLetter))
333+ where startsWithLetter (Tok _ Word t) =
335+ Just (c, _) | isLetter c -> True
337+ startsWithLetter _ = False
339+quoted' :: PandocMonad m
340+ => (Inlines -> Inlines)
344+quoted' f starter ender = do
345+ startchs <- (T.unpack . untokenize) <$> starter
346+ smart <- extensionEnabled Ext_smart <$> getOption readerExtensions
349+ ils <- many (notFollowedBy ender >> inline)
350+ (ender >> return (f (mconcat ils))) <|>
351+ (<> mconcat ils) <$>
352+ lit (case startchs of
358+inline :: PandocMonad m => LP m Inlines
359+inline = (mempty <$ comment)
360+ <|> (space <$ whitespace)
361+ <|> (softbreak <$ endline)
363+ -- <|> inlineCommand
364+ -- <|> inlineEnvironment
367+ option (str "-") (symbol '-' *>
368+ option (str "–") (str "—" <$ symbol '-')))
371+ <|> (str "”" <$ try (symbol '\'' >> symbol '\''))
372+ <|> (str "”" <$ symbol '”')
373+ <|> (str "’" <$ symbol '\'')
374+ <|> (str "’" <$ symbol '’')
375+ <|> (str "\160" <$ symbol '~')
376+ -- <|> mathDisplay (string "$$" *> mathChars <* string "$$")
377+ -- <|> mathInline (char '$' *> mathChars <* char '$')
378+ <|> (guardEnabled Ext_literate_haskell *> symbol '|' *> doLHSverb)
379+ <|> (str . (:[]) <$> primEscape)
380+ <|> (do res <- symbolIn "#&~^'`\"[]"
382+ let s = T.unpack (untoken res)
383+ report $ ParsingUnescaped s pos
386+inlines :: PandocMonad m => LP m Inlines
387+inlines = mconcat <$> many inline
389+-- PREVIOUSLY HAD (notFollowedBy (char '}') *> inline)
393+paragraph :: PandocMonad m => LP m Blocks
395+ x <- trimInlines . mconcat <$> many1 inline
398+ else return $ para x
400+block :: PandocMonad m => LP m Blocks
401+block = (mempty <$ spaces1)
409+blocks :: PandocMonad m => LP m Blocks
410+blocks = mconcat <$> many block
60-- | Parse LaTeX from string and return 'Pandoc' document.
418-- | Parse LaTeX from string and return 'Pandoc' document.
61readLaTeX :: PandocMonad m
419readLaTeX :: PandocMonad m
62 => ReaderOptions -- ^ Reader options
420 => ReaderOptions -- ^ Reader options
@@ -69,6 +427,7 @@ readLaTeX opts ltx = do
69 Right result -> return result
427 Right result -> return result
70 Left e -> throwError e
428 Left e -> throwError e
72parseLaTeX :: PandocMonad m => LP m Pandoc
431parseLaTeX :: PandocMonad m => LP m Pandoc
73parseLaTeX = do
432parseLaTeX = do
74 bs <- blocks
433 bs <- blocks
@@ -88,8 +447,6 @@ parseLaTeX = do
88 else id) doc'
447 else id) doc'
89 return $ Pandoc meta bs'
448 return $ Pandoc meta bs'
91−type LP m = ParserT String ParserState m
93anyControlSeq :: PandocMonad m => LP m String
450anyControlSeq :: PandocMonad m => LP m String
94anyControlSeq = do
451anyControlSeq = do
95 char '\\'
452 char '\\'
@@ -127,8 +484,8 @@ endline = try (newline >> lookAhead anyChar >> notFollowedBy blankline)
127isLowerHex :: Char -> Bool
484isLowerHex :: Char -> Bool
128isLowerHex x = x >= '0' && x <= '9' || x >= 'a' && x <= 'f'
485isLowerHex x = x >= '0' && x <= '9' || x >= 'a' && x <= 'f'
130−tildeEscape :: PandocMonad m => LP m Char
487+primEscape :: PandocMonad m => LP m Char
131−tildeEscape = try $ do
488+primEscape = try $ do
132 string "^^"
489 string "^^"
133 c <- satisfy (\x -> x >= '\0' && x <= '\128')
490 c <- satisfy (\x -> x >= '\0' && x <= '\128')
134 d <- if isLowerHex c
491 d <- if isLowerHex c
@@ -200,37 +557,6 @@ mathChars =
200 isOrdChar '\\' = False
557 isOrdChar '\\' = False
201 isOrdChar _ = True
558 isOrdChar _ = True
203−quoted' :: PandocMonad m => (Inlines -> Inlines) -> LP m String -> LP m () -> LP m Inlines
204−quoted' f starter ender = do
206− smart <- extensionEnabled Ext_smart <$> getOption readerExtensions
209− ils <- many (notFollowedBy ender >> inline)
210− (ender >> return (f (mconcat ils))) <|>
211− (<> mconcat ils) <$>
212− lit (case startchs of
218−doubleQuote :: PandocMonad m => LP m Inlines
220− quoted' doubleQuoted (try $ string "``") (void $ try $ string "''")
221− <|> quoted' doubleQuoted (string "“") (void $ char '”')
222− -- the following is used by babel for localized quotes:
223− <|> quoted' doubleQuoted (try $ string "\"`") (void $ try $ string "\"'")
224− <|> quoted' doubleQuoted (string "\"") (void $ char '"')
226−singleQuote :: PandocMonad m => LP m Inlines
228− smart <- extensionEnabled Ext_smart <$> getOption readerExtensions
230− then quoted' singleQuoted (string "`") (try $ char '\'' >> notFollowedBy letter)
231− <|> quoted' singleQuoted (string "‘") (try $ char '’' >> notFollowedBy letter)
232− else str <$> many1 (oneOf "`\'‘’")
234inline :: PandocMonad m => LP m Inlines
560inline :: PandocMonad m => LP m Inlines
235inline = (mempty <$ comment)
561inline = (mempty <$ comment)
236 <|> (space <$ whitespace)
562 <|> (space <$ whitespace)
@@ -251,7 +577,7 @@ inline = (mempty <$ comment)
251 <|> mathDisplay (string "$$" *> mathChars <* string "$$")
577 <|> mathDisplay (string "$$" *> mathChars <* string "$$")
252 <|> mathInline (char '$' *> mathChars <* char '$')
578 <|> mathInline (char '$' *> mathChars <* char '$')
253 <|> (guardEnabled Ext_literate_haskell *> char '|' *> doLHSverb)
579 <|> (guardEnabled Ext_literate_haskell *> char '|' *> doLHSverb)
254− <|> (str . (:[]) <$> tildeEscape)
580+ <|> (str . (:[]) <$> primEscape)
255 <|> (do res <- oneOf "#&~^'`\"[]"
581 <|> (do res <- oneOf "#&~^'`\"[]"
256 pos <- getPosition
582 pos <- getPosition
257 report $ ParsingUnescaped [res] pos
583 report $ ParsingUnescaped [res] pos
@@ -744,9 +1070,6 @@ dolstinline = do
744 marker <- char '{' <|> anyChar
1070 marker <- char '{' <|> anyChar
745 codeWith ("",classes,[]) <$> manyTill (satisfy (/='\n')) (char '}' <|> char marker)
1071 codeWith ("",classes,[]) <$> manyTill (satisfy (/='\n')) (char '}' <|> char marker)
747−doLHSverb :: PandocMonad m => LP m Inlines
748−doLHSverb = codeWith ("",["haskell"],[]) <$> manyTill (satisfy (/='\n')) (char '|')
750-- converts e.g. \SI{1}[\$]{} to "$ 1" or \SI{1}{\euro} to "1 €"
1073-- converts e.g. \SI{1}[\$]{} to "$ 1" or \SI{1}{\euro} to "1 €"
751dosiunitx :: PandocMonad m => LP m Inlines
1074dosiunitx :: PandocMonad m => LP m Inlines
752dosiunitx = do
1075dosiunitx = do
@@ -762,9 +1085,6 @@ dosiunitx = do
762 emptyOr160 unit,
1085 emptyOr160 unit,
765−lit :: String -> LP m Inlines
768accent :: (Char -> String) -> Inlines -> LP m Inlines
1088accent :: (Char -> String) -> Inlines -> LP m Inlines
769accent f ils =
1089accent f ils =
770 case toList ils of
1090 case toList ils of
@@ -1570,3 +1890,4 @@ removeDoubleQuotes ('"':xs) =
1570 '"':ys -> reverse ys
1890 '"':ys -> reverse ys
1571 _ -> '"':xs
1891 _ -> '"':xs
1572removeDoubleQuotes xs = xs
1892removeDoubleQuotes xs = xs