diff options
Diffstat (limited to 'src/Text/Pandoc/Writers/LaTeX.hs')
-rw-r--r-- | src/Text/Pandoc/Writers/LaTeX.hs | 349 |
1 files changed, 255 insertions, 94 deletions
diff --git a/src/Text/Pandoc/Writers/LaTeX.hs b/src/Text/Pandoc/Writers/LaTeX.hs index 720c00ac8..28a1e7174 100644 --- a/src/Text/Pandoc/Writers/LaTeX.hs +++ b/src/Text/Pandoc/Writers/LaTeX.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE OverloadedStrings #-} {- Copyright (C) 2006-2010 John MacFarlane <jgm@berkeley.edu> @@ -29,13 +30,15 @@ Conversion of 'Pandoc' format into LaTeX. -} module Text.Pandoc.Writers.LaTeX ( writeLaTeX ) where import Text.Pandoc.Definition +import Text.Pandoc.Generic import Text.Pandoc.Shared import Text.Pandoc.Templates import Text.Printf ( printf ) -import Data.List ( (\\), isSuffixOf, isPrefixOf, intersperse ) -import Data.Char ( toLower ) +import Data.List ( (\\), isSuffixOf, isPrefixOf, intercalate, intersperse ) +import Data.Char ( toLower, isPunctuation ) import Control.Monad.State -import Text.PrettyPrint.HughesPJ hiding ( Str ) +import Text.Pandoc.Pretty +import System.FilePath (dropExtension) data WriterState = WriterState { stInNote :: Bool -- @True@ if we're in a note @@ -60,7 +63,7 @@ writeLaTeX options document = stVerbInNote = False, stEnumerate = False, stTable = False, stStrikeout = False, stSubscript = False, stUrl = False, stGraphics = False, - stLHS = False, stBook = False } + stLHS = False, stBook = writerChapters options } pandocToLaTeX :: WriterOptions -> Pandoc -> State WriterState String pandocToLaTeX options (Pandoc (Meta title authors date) blocks) = do @@ -70,13 +73,34 @@ pandocToLaTeX options (Pandoc (Meta title authors date) blocks) = do "{report}" `isSuffixOf` x) when (any usesBookClass (lines template)) $ modify $ \s -> s{stBook = True} - titletext <- liftM render $ inlineListToLaTeX title - authorsText <- mapM (liftM render . inlineListToLaTeX) authors - dateText <- liftM render $ inlineListToLaTeX date - body <- blockListToLaTeX blocks - let main = render body + opts <- liftM stOptions get + let colwidth = if writerWrapText opts + then Just $ writerColumns opts + else Nothing + titletext <- liftM (render colwidth) $ inlineListToLaTeX title + authorsText <- mapM (liftM (render colwidth) . inlineListToLaTeX) authors + dateText <- liftM (render colwidth) $ inlineListToLaTeX date + let (blocks', lastHeader) = if writerCiteMethod options == Citeproc then + (blocks, []) + else case last blocks of + Header 1 il -> (init blocks, il) + _ -> (blocks, []) + body <- blockListToLaTeX blocks' + biblioTitle <- liftM (render colwidth) $ inlineListToLaTeX lastHeader + let main = render colwidth body st <- get - let context = writerVariables options ++ + let biblioFiles = intercalate "," $ map dropExtension $ writerBiblioFiles options + citecontext = case writerCiteMethod options of + Natbib -> [ ("biblio-files", biblioFiles) + , ("biblio-title", biblioTitle) + , ("natbib", "yes") + ] + Biblatex -> [ ("biblio-files", biblioFiles) + , ("biblio-title", biblioTitle) + , ("biblatex", "yes") + ] + _ -> [] + context = writerVariables options ++ [ ("toc", if writerTableOfContents options then "yes" else "") , ("body", main) , ("title", titletext) @@ -91,7 +115,10 @@ pandocToLaTeX options (Pandoc (Meta title authors date) blocks) = do [ ("url", "yes") | stUrl st ] ++ [ ("numbersections", "yes") | writerNumberSections options ] ++ [ ("lhs", "yes") | stLHS st ] ++ - [ ("graphics", "yes") | stGraphics st ] + [ ("graphics", "yes") | stGraphics st ] ++ + [ ("book-class", "yes") | stBook st] ++ + [ ("listings", "yes") | writerListings options ] ++ + citecontext return $ if writerStandalone options then renderTemplate context template else main @@ -107,7 +134,13 @@ stringToLaTeX = escapeStringUsing latexEscapes , ('|', "\\textbar{}") , ('<', "\\textless{}") , ('>', "\\textgreater{}") + , ('[', "{[}") -- to avoid interpretation as + , (']', "{]}") -- optional arguments , ('\160', "~") + , ('\x2018', "`") + , ('\x2019', "'") + , ('\x201C', "``") + , ('\x201D', "''") ] -- | Puts contents into LaTeX command. @@ -118,49 +151,73 @@ inCmd cmd contents = char '\\' <> text cmd <> braces contents -- (because it's illegal to have verbatim inside some command arguments) deVerb :: [Inline] -> [Inline] deVerb [] = [] -deVerb ((Code str):rest) = - (TeX $ "\\texttt{" ++ stringToLaTeX str ++ "}"):(deVerb rest) +deVerb ((Code _ str):rest) = + (RawInline "latex" $ "\\texttt{" ++ stringToLaTeX str ++ "}"):(deVerb rest) deVerb (other:rest) = other:(deVerb rest) -- | Convert Pandoc block element to LaTeX. blockToLaTeX :: Block -- ^ Block to convert -> State WriterState Doc blockToLaTeX Null = return empty -blockToLaTeX (Plain lst) = do - st <- get - let opts = stOptions st - wrapTeXIfNeeded opts True inlineListToLaTeX lst +blockToLaTeX (Plain lst) = inlineListToLaTeX lst blockToLaTeX (Para [Image txt (src,tit)]) = do capt <- inlineListToLaTeX txt img <- inlineToLaTeX (Image txt (src,tit)) - return $ text "\\begin{figure}[htb]" $$ text "\\centering" $$ img $$ - (text "\\caption{" <> capt <> char '}') $$ text "\\end{figure}\n" + return $ "\\begin{figure}[htb]" $$ "\\centering" $$ img $$ + ("\\caption{" <> capt <> char '}') $$ "\\end{figure}" $$ blankline blockToLaTeX (Para lst) = do - st <- get - let opts = stOptions st - result <- wrapTeXIfNeeded opts True inlineListToLaTeX lst - return $ result <> char '\n' + result <- inlineListToLaTeX lst + return $ result <> blankline blockToLaTeX (BlockQuote lst) = do contents <- blockListToLaTeX lst - return $ text "\\begin{quote}" $$ contents $$ text "\\end{quote}" -blockToLaTeX (CodeBlock (_,classes,_) str) = do + return $ "\\begin{quote}" $$ contents $$ "\\end{quote}" +blockToLaTeX (CodeBlock (_,classes,keyvalAttr) str) = do st <- get env <- if writerLiterateHaskell (stOptions st) && "haskell" `elem` classes && "literate" `elem` classes then do modify $ \s -> s{ stLHS = True } return "code" - else if stInNote st - then do - modify $ \s -> s{ stVerbInNote = True } - return "Verbatim" - else return "verbatim" - return $ text ("\\begin{" ++ env ++ "}\n") <> text str <> - text ("\n\\end{" ++ env ++ "}") -blockToLaTeX (RawHtml _) = return empty + else if writerListings (stOptions st) + then return "lstlisting" + else if stInNote st + then do + modify $ \s -> s{ stVerbInNote = True } + return "Verbatim" + else return "verbatim" + let params = if writerListings (stOptions st) + then take 1 + [ "language=" ++ lang | lang <- classes + , lang `elem` ["ABAP","IDL","Plasm","ACSL","inform" + ,"POV","Ada","Java","Prolog","Algol" + ,"JVMIS","Promela","Ant","ksh","Python" + ,"Assembler","Lisp","R","Awk","Logo" + ,"Reduce","bash","make","Rexx","Basic" + ,"Mathematica","RSL","C","Matlab","Ruby" + ,"C++","Mercury","S","Caml","MetaPost" + ,"SAS","Clean","Miranda","Scilab","Cobol" + ,"Mizar","sh","Comal","ML","SHELXL","csh" + ,"Modula-2","Simula","Delphi","MuPAD" + ,"SQL","Eiffel","NASTRAN","tcl","Elan" + ,"Oberon-2","TeX","erlang","OCL" + ,"VBScript","Euphoria","Octave","Verilog" + ,"Fortran","Oz","VHDL","GCL","Pascal" + ,"VRML","Gnuplot","Perl","XML","Haskell" + ,"PHP","XSLT","HTML","PL/I"] + ] ++ + [ key ++ "=" ++ attr | (key,attr) <- keyvalAttr ] + else [] + printParams + | null params = empty + | otherwise = "[" <> hsep (intersperse "," (map text params)) <> + "]" + return $ "\\begin{" <> text env <> "}" <> printParams $$ flush (text str) $$ + "\\end{" <> text env <> "}" $$ cr -- final cr needed because of footnotes +blockToLaTeX (RawBlock "latex" x) = return $ text x <> blankline +blockToLaTeX (RawBlock _ _) = return empty blockToLaTeX (BulletList lst) = do items <- mapM listItemToLaTeX lst - return $ text "\\begin{itemize}" $$ vcat items $$ text "\\end{itemize}" + return $ "\\begin{itemize}" $$ vcat items $$ "\\end{itemize}" blockToLaTeX (OrderedList (start, numstyle, numdelim) lst) = do st <- get let oldlevel = stOLLevel st @@ -179,20 +236,19 @@ blockToLaTeX (OrderedList (start, numstyle, numdelim) lst) = do map toLower (toRomanNumeral oldlevel) ++ "}{" ++ show (start - 1) ++ "}" else empty - return $ text "\\begin{enumerate}" <> exemplar $$ resetcounter $$ - vcat items $$ text "\\end{enumerate}" + return $ "\\begin{enumerate}" <> exemplar $$ resetcounter $$ + vcat items $$ "\\end{enumerate}" blockToLaTeX (DefinitionList lst) = do items <- mapM defListItemToLaTeX lst - return $ text "\\begin{description}" $$ vcat items $$ - text "\\end{description}" -blockToLaTeX HorizontalRule = return $ text $ - "\\begin{center}\\rule{3in}{0.4pt}\\end{center}\n" + return $ "\\begin{description}" $$ vcat items $$ "\\end{description}" +blockToLaTeX HorizontalRule = return $ + "\\begin{center}\\rule{3in}{0.4pt}\\end{center}" $$ blankline blockToLaTeX (Header level lst) = do let lst' = deVerb lst txt <- inlineListToLaTeX lst' let noNote (Note _) = Str "" noNote x = x - let lstNoNotes = processWith noNote lst' + let lstNoNotes = bottomUp noNote lst' -- footnotes in sections don't work unless you specify an optional -- argument: \section[mysec]{mysec\footnote{blah}} optional <- if lstNoNotes == lst' @@ -202,30 +258,31 @@ blockToLaTeX (Header level lst) = do return $ char '[' <> res <> char ']' let stuffing = optional <> char '{' <> txt <> char '}' book <- liftM stBook get - return $ case (book, level) of - (True, 1) -> text "\\chapter" <> stuffing <> char '\n' - (True, 2) -> text "\\section" <> stuffing <> char '\n' - (True, 3) -> text "\\subsection" <> stuffing <> char '\n' - (True, 4) -> text "\\subsubsection" <> stuffing <> char '\n' - (False, 1) -> text "\\section" <> stuffing <> char '\n' - (False, 2) -> text "\\subsection" <> stuffing <> char '\n' - (False, 3) -> text "\\subsubsection" <> stuffing <> char '\n' - _ -> txt <> char '\n' + let level' = if book then level - 1 else level + let headerWith x y = text x <> y $$ blankline + return $ case level' of + 0 -> headerWith "\\chapter" stuffing + 1 -> headerWith "\\section" stuffing + 2 -> headerWith "\\subsection" stuffing + 3 -> headerWith "\\subsubsection" stuffing + 4 -> headerWith "\\paragraph" stuffing + 5 -> headerWith "\\subparagraph" stuffing + _ -> txt $$ blankline blockToLaTeX (Table caption aligns widths heads rows) = do headers <- if all null heads then return empty - else liftM ($$ text "\\hline") $ tableRowToLaTeX heads + else liftM ($$ "\\hline") $ (tableRowToLaTeX widths) heads captionText <- inlineListToLaTeX caption - rows' <- mapM tableRowToLaTeX rows + rows' <- mapM (tableRowToLaTeX widths) rows let colDescriptors = concat $ zipWith toColDescriptor widths aligns let tableBody = text ("\\begin{tabular}{" ++ colDescriptors ++ "}") $$ - headers $$ vcat rows' $$ text "\\end{tabular}" - let centered txt = text "\\begin{center}" $$ txt $$ text "\\end{center}" + headers $$ vcat rows' $$ "\\end{tabular}" + let centered txt = "\\begin{center}" $$ txt $$ "\\end{center}" modify $ \s -> s{ stTable = True } return $ if isEmpty captionText - then centered tableBody <> char '\n' - else text "\\begin{table}[h]" $$ centered tableBody $$ - inCmd "caption" captionText $$ text "\\end{table}\n" + then centered tableBody $$ blankline + else "\\begin{table}[h]" $$ centered tableBody $$ + inCmd "caption" captionText $$ "\\end{table}" $$ blankline toColDescriptor :: Double -> Alignment -> String toColDescriptor 0 align = @@ -240,16 +297,19 @@ toColDescriptor width align = ">{\\PBS" ++ AlignRight -> "\\raggedleft" AlignCenter -> "\\centering" AlignDefault -> "\\raggedright") ++ - "\\hspace{0pt}}p{" ++ printf "%.2f" width ++ - "\\columnwidth}" + "\\hspace{0pt}}p{" ++ printf "%.2f" width ++ "\\columnwidth}" blockListToLaTeX :: [Block] -> State WriterState Doc blockListToLaTeX lst = mapM blockToLaTeX lst >>= return . vcat -tableRowToLaTeX :: [[Block]] -> State WriterState Doc -tableRowToLaTeX cols = mapM blockListToLaTeX cols >>= - return . ($$ text "\\\\") . foldl (\row item -> row $$ - (if isEmpty row then text "" else text " & ") <> item) empty +tableRowToLaTeX :: [Double] -> [[Block]] -> State WriterState Doc +tableRowToLaTeX widths cols = do + renderedCells <- mapM blockListToLaTeX cols + let toCell 0 c = c + toCell w c = "\\parbox{" <> text (printf "%.2f" w) <> + "\\columnwidth}{" <> c <> cr <> "}" + let cells = zipWith toCell widths renderedCells + return $ (hcat $ intersperse (" & ") cells) <> "\\\\" listItemToLaTeX :: [Block] -> State WriterState Doc listItemToLaTeX lst = blockListToLaTeX lst >>= return . (text "\\item" $$) . @@ -258,8 +318,8 @@ listItemToLaTeX lst = blockListToLaTeX lst >>= return . (text "\\item" $$) . defListItemToLaTeX :: ([Inline], [[Block]]) -> State WriterState Doc defListItemToLaTeX (term, defs) = do term' <- inlineListToLaTeX $ deVerb term - def' <- liftM (vcat . intersperse (text "")) $ mapM blockListToLaTeX defs - return $ text "\\item[" <> term' <> text "]" $$ def' + def' <- liftM vsep $ mapM blockListToLaTeX defs + return $ "\\item" <> brackets term' $$ def' -- | Convert list of inline elements to LaTeX. inlineListToLaTeX :: [Inline] -- ^ Inlines to convert @@ -292,60 +352,161 @@ inlineToLaTeX (Subscript lst) = do return $ inCmd "textsubscr" contents inlineToLaTeX (SmallCaps lst) = inlineListToLaTeX (deVerb lst) >>= return . inCmd "textsc" -inlineToLaTeX (Cite _ lst) = - inlineListToLaTeX lst -inlineToLaTeX (Code str) = do +inlineToLaTeX (Cite cits lst) = do + st <- get + let opts = stOptions st + case writerCiteMethod opts of + Natbib -> citationsToNatbib cits + Biblatex -> citationsToBiblatex cits + _ -> inlineListToLaTeX lst + +inlineToLaTeX (Code _ str) = do st <- get when (stInNote st) $ modify $ \s -> s{ stVerbInNote = True } let chr = ((enumFromTo '!' '~') \\ str) !! 0 - return $ text $ "\\verb" ++ [chr] ++ str ++ [chr] + if writerListings (stOptions st) + then return $ text $ "\\lstinline" ++ [chr] ++ str ++ [chr] + else return $ text $ "\\verb" ++ [chr] ++ str ++ [chr] inlineToLaTeX (Quoted SingleQuote lst) = do contents <- inlineListToLaTeX lst let s1 = if (not (null lst)) && (isQuoted (head lst)) - then text "\\," - else empty + then "\\," + else empty let s2 = if (not (null lst)) && (isQuoted (last lst)) - then text "\\," + then "\\," else empty return $ char '`' <> s1 <> contents <> s2 <> char '\'' inlineToLaTeX (Quoted DoubleQuote lst) = do contents <- inlineListToLaTeX lst let s1 = if (not (null lst)) && (isQuoted (head lst)) - then text "\\," - else empty + then "\\," + else empty let s2 = if (not (null lst)) && (isQuoted (last lst)) - then text "\\," + then "\\," else empty - return $ text "``" <> s1 <> contents <> s2 <> text "''" + return $ "``" <> s1 <> contents <> s2 <> "''" inlineToLaTeX Apostrophe = return $ char '\'' -inlineToLaTeX EmDash = return $ text "---" -inlineToLaTeX EnDash = return $ text "--" -inlineToLaTeX Ellipses = return $ text "\\ldots{}" +inlineToLaTeX EmDash = return "---" +inlineToLaTeX EnDash = return "--" +inlineToLaTeX Ellipses = return "\\ldots{}" inlineToLaTeX (Str str) = return $ text $ stringToLaTeX str inlineToLaTeX (Math InlineMath str) = return $ char '$' <> text str <> char '$' -inlineToLaTeX (Math DisplayMath str) = return $ text "\\[" <> text str <> text "\\]" -inlineToLaTeX (TeX str) = return $ text str -inlineToLaTeX (HtmlInline _) = return empty -inlineToLaTeX (LineBreak) = return $ text "\\\\" -inlineToLaTeX Space = return $ char ' ' +inlineToLaTeX (Math DisplayMath str) = return $ "\\[" <> text str <> "\\]" +inlineToLaTeX (RawInline "latex" str) = return $ text str +inlineToLaTeX (RawInline "tex" str) = return $ text str +inlineToLaTeX (RawInline _ _) = return empty +inlineToLaTeX (LineBreak) = return "\\\\" +inlineToLaTeX Space = return space inlineToLaTeX (Link txt (src, _)) = case txt of - [Code x] | x == src -> -- autolink + [Code _ x] | x == src -> -- autolink do modify $ \s -> s{ stUrl = True } return $ text $ "\\url{" ++ x ++ "}" _ -> do contents <- inlineListToLaTeX $ deVerb txt - return $ text ("\\href{" ++ src ++ "}{") <> contents <> - char '}' + return $ text ("\\href{" ++ stringToLaTeX src ++ "}{") <> + contents <> char '}' inlineToLaTeX (Image _ (source, _)) = do modify $ \s -> s{ stGraphics = True } - return $ text $ "\\includegraphics{" ++ source ++ "}" + return $ "\\includegraphics" <> braces (text source) inlineToLaTeX (Note contents) = do - st <- get - put (st {stInNote = True}) + modify (\s -> s{stInNote = True}) contents' <- blockListToLaTeX contents modify (\s -> s {stInNote = False}) - let rawnote = stripTrailingNewlines $ render contents' -- note: a \n before } is needed when note ends with a Verbatim environment - let optNewline = "\\end{Verbatim}" `isSuffixOf` rawnote - return $ text "\\footnote{" <> - text rawnote <> (if optNewline then char '\n' else empty) <> char '}' + return $ "\\footnote" <> braces (nest 2 contents') + + +citationsToNatbib :: [Citation] -> State WriterState Doc +citationsToNatbib (one:[]) + = citeCommand c p s k + where + Citation { citationId = k + , citationPrefix = p + , citationSuffix = s + , citationMode = m + } + = one + c = case m of + AuthorInText -> "citet" + SuppressAuthor -> "citeyearpar" + NormalCitation -> "citep" + +citationsToNatbib cits + | noPrefix (tail cits) && noSuffix (init cits) && ismode NormalCitation cits + = citeCommand "citep" p s ks + where + noPrefix = and . map (null . citationPrefix) + noSuffix = and . map (null . citationSuffix) + ismode m = and . map (((==) m) . citationMode) + p = citationPrefix $ head $ cits + s = citationSuffix $ last $ cits + ks = intercalate ", " $ map citationId cits + +citationsToNatbib (c:cs) | citationMode c == AuthorInText = do + author <- citeCommand "citeauthor" [] [] (citationId c) + cits <- citationsToNatbib (c { citationMode = SuppressAuthor } : cs) + return $ author <+> cits + +citationsToNatbib cits = do + cits' <- mapM convertOne cits + return $ text "\\citetext{" <> foldl combineTwo empty cits' <> text "}" + where + combineTwo a b | isEmpty a = b + | otherwise = a <> text "; " <> b + convertOne Citation { citationId = k + , citationPrefix = p + , citationSuffix = s + , citationMode = m + } + = case m of + AuthorInText -> citeCommand "citealt" p s k + SuppressAuthor -> citeCommand "citeyear" p s k + NormalCitation -> citeCommand "citealp" p s k + +citeCommand :: String -> [Inline] -> [Inline] -> String -> State WriterState Doc +citeCommand c p s k = do + args <- citeArguments p s k + return $ text ("\\" ++ c) <> args + +citeArguments :: [Inline] -> [Inline] -> String -> State WriterState Doc +citeArguments p s k = do + let s' = case s of + (Str (x:[]) : r) | isPunctuation x -> dropWhile (== Space) r + (Str (x:xs) : r) | isPunctuation x -> Str xs : r + _ -> s + pdoc <- inlineListToLaTeX p + sdoc <- inlineListToLaTeX s' + let optargs = case (isEmpty pdoc, isEmpty sdoc) of + (True, True ) -> empty + (True, False) -> brackets sdoc + (_ , _ ) -> brackets pdoc <> brackets sdoc + return $ optargs <> braces (text k) + +citationsToBiblatex :: [Citation] -> State WriterState Doc +citationsToBiblatex (one:[]) + = citeCommand cmd p s k + where + Citation { citationId = k + , citationPrefix = p + , citationSuffix = s + , citationMode = m + } = one + cmd = case m of + SuppressAuthor -> "autocite*" + AuthorInText -> "textcite" + NormalCitation -> "autocite" + +citationsToBiblatex (c:cs) = do + args <- mapM convertOne (c:cs) + return $ text cmd <> foldl (<>) empty args + where + cmd = case citationMode c of + AuthorInText -> "\\textcites" + _ -> "\\autocites" + convertOne Citation { citationId = k + , citationPrefix = p + , citationSuffix = s + } + = citeArguments p s k + +citationsToBiblatex _ = return empty |