2009-12-31 01:08:38 +00:00
|
|
|
{-
|
|
|
|
Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
-}
|
|
|
|
|
|
|
|
{- |
|
|
|
|
Module : Text.Pandoc.Templates
|
|
|
|
Copyright : Copyright (C) 2009 John MacFarlane
|
|
|
|
License : GNU GPL, version 2 or above
|
|
|
|
|
|
|
|
Maintainer : John MacFarlane <jgm@berkeley.edu>
|
|
|
|
Stability : alpha
|
|
|
|
Portability : portable
|
|
|
|
|
|
|
|
A simple templating system with variable substitution and conditionals.
|
|
|
|
Example:
|
|
|
|
|
|
|
|
> > renderTemplate [("name","Sam"),("salary","50,000")] $
|
|
|
|
> "Hi, $name$. $if(salary)$You make $$$salary$.$else$No salary data.$endif$"
|
|
|
|
> > "Hi, John. You make $50,000."
|
|
|
|
|
|
|
|
A slot for an interpolated variable is a variable name surrounded
|
|
|
|
by dollar signs. To include a literal @$@ in your template, use
|
|
|
|
@$$@. Variable names must begin with a letter and can contain letters,
|
|
|
|
numbers, @_@, and @-@.
|
|
|
|
|
|
|
|
A conditional begins with @$if(variable_name)$@ and ends with @$endif$@.
|
|
|
|
It may optionally contain an @$else$@ section. The if section is
|
|
|
|
used if @variable_name@ has a non-null value, otherwise the else section
|
|
|
|
is used.
|
|
|
|
-}
|
|
|
|
|
2009-12-31 01:10:49 +00:00
|
|
|
module Text.Pandoc.Templates (renderTemplate, getDefaultTemplate) where
|
2009-12-31 01:08:38 +00:00
|
|
|
|
|
|
|
import Text.ParserCombinators.Parsec
|
2009-12-31 01:14:04 +00:00
|
|
|
import Control.Monad (liftM, when)
|
2009-12-31 01:10:49 +00:00
|
|
|
import qualified Control.Exception as E (try, IOException)
|
|
|
|
import System.FilePath
|
2009-12-31 01:11:23 +00:00
|
|
|
import Text.Pandoc.Shared (readDataFile)
|
2009-12-31 01:14:04 +00:00
|
|
|
import Data.List (intercalate)
|
2009-12-31 01:10:49 +00:00
|
|
|
|
|
|
|
-- | Get the default template, either from the application's user data
|
|
|
|
-- directory (~/.pandoc on unix) or from the cabal data directory.
|
|
|
|
getDefaultTemplate :: String -> IO (Either E.IOException String)
|
2009-12-31 01:10:57 +00:00
|
|
|
getDefaultTemplate "native" = return $ Right ""
|
2009-12-31 01:13:41 +00:00
|
|
|
getDefaultTemplate "s5" = getDefaultTemplate "html"
|
2009-12-31 01:10:57 +00:00
|
|
|
getDefaultTemplate "odt" = getDefaultTemplate "opendocument"
|
2009-12-31 01:10:49 +00:00
|
|
|
getDefaultTemplate format = do
|
2009-12-31 01:10:57 +00:00
|
|
|
let format' = takeWhile (/='+') format -- strip off "+lhs" if present
|
2009-12-31 01:11:30 +00:00
|
|
|
E.try $ readDataFile $ "templates" </> format' <.> "template"
|
2009-12-31 01:14:04 +00:00
|
|
|
|
|
|
|
data TemplateState = TemplateState Int [(String,String)]
|
|
|
|
|
|
|
|
adjustPosition :: String -> GenParser Char TemplateState String
|
|
|
|
adjustPosition str = do
|
|
|
|
let lastline = takeWhile (/= '\n') $ reverse str
|
|
|
|
updateState $ \(TemplateState pos x) ->
|
|
|
|
if str == lastline
|
|
|
|
then TemplateState (pos + length lastline) x
|
|
|
|
else TemplateState (length lastline) x
|
|
|
|
return str
|
|
|
|
|
2009-12-31 01:08:38 +00:00
|
|
|
-- | Renders a template
|
|
|
|
renderTemplate :: [(String,String)] -- ^ Assoc. list of values for variables
|
|
|
|
-> String -- ^ Template
|
|
|
|
-> String
|
|
|
|
renderTemplate vals templ =
|
2009-12-31 01:14:04 +00:00
|
|
|
case runParser (do x <- parseTemplate; eof; return x) (TemplateState 0 vals) "template" templ of
|
2009-12-31 01:13:26 +00:00
|
|
|
Left e -> error $ show e
|
2009-12-31 01:08:38 +00:00
|
|
|
Right r -> concat r
|
|
|
|
|
|
|
|
reservedWords :: [String]
|
|
|
|
reservedWords = ["else","endif"]
|
|
|
|
|
2009-12-31 01:14:04 +00:00
|
|
|
parseTemplate :: GenParser Char TemplateState [String]
|
2009-12-31 01:08:38 +00:00
|
|
|
parseTemplate =
|
2009-12-31 01:14:04 +00:00
|
|
|
many $ (plaintext <|> escapedDollar <|> conditional <|> variable)
|
|
|
|
>>= adjustPosition
|
2009-12-31 01:08:38 +00:00
|
|
|
|
2009-12-31 01:14:04 +00:00
|
|
|
plaintext :: GenParser Char TemplateState String
|
|
|
|
plaintext = many1 $ noneOf "$"
|
2009-12-31 01:08:38 +00:00
|
|
|
|
2009-12-31 01:14:04 +00:00
|
|
|
escapedDollar :: GenParser Char TemplateState String
|
2009-12-31 01:08:38 +00:00
|
|
|
escapedDollar = try $ string "$$" >> return "$"
|
|
|
|
|
2009-12-31 01:14:04 +00:00
|
|
|
conditional :: GenParser Char TemplateState String
|
2009-12-31 01:08:38 +00:00
|
|
|
conditional = try $ do
|
2009-12-31 01:14:04 +00:00
|
|
|
TemplateState pos vars <- getState
|
2009-12-31 01:08:38 +00:00
|
|
|
string "$if("
|
|
|
|
id' <- ident
|
|
|
|
string ")$"
|
2009-12-31 01:14:04 +00:00
|
|
|
-- if newline after the "if", then a newline after "endif" will be swallowed
|
|
|
|
multiline <- option False $ try $
|
|
|
|
newline >> count pos (char ' ') >> return True
|
|
|
|
let conditionSatisfied = case lookup id' vars of
|
|
|
|
Nothing -> False
|
|
|
|
Just "" -> False
|
|
|
|
Just _ -> True
|
|
|
|
contents <- if conditionSatisfied
|
|
|
|
then liftM concat parseTemplate
|
|
|
|
else do
|
|
|
|
parseTemplate -- skip if part, then reset position
|
|
|
|
setState $ TemplateState pos vars
|
|
|
|
option "" $ do try (string "$else$")
|
|
|
|
optional newline
|
|
|
|
liftM concat parseTemplate
|
2009-12-31 01:08:38 +00:00
|
|
|
string "$endif$"
|
2009-12-31 01:14:04 +00:00
|
|
|
when multiline $ optional $ newline
|
|
|
|
return contents
|
|
|
|
|
|
|
|
ident :: GenParser Char TemplateState String
|
2009-12-31 01:08:38 +00:00
|
|
|
ident = do
|
|
|
|
first <- letter
|
|
|
|
rest <- many (alphaNum <|> oneOf "_-")
|
|
|
|
let id' = first : rest
|
|
|
|
if id' `elem` reservedWords
|
|
|
|
then pzero
|
|
|
|
else return id'
|
|
|
|
|
2009-12-31 01:14:04 +00:00
|
|
|
variable :: GenParser Char TemplateState String
|
2009-12-31 01:08:38 +00:00
|
|
|
variable = try $ do
|
|
|
|
char '$'
|
|
|
|
id' <- ident
|
|
|
|
char '$'
|
2009-12-31 01:14:04 +00:00
|
|
|
TemplateState pos vars <- getState
|
|
|
|
let indent = replicate pos ' '
|
|
|
|
return $ case lookup id' vars of
|
|
|
|
Just val -> intercalate ('\n' : indent) $ lines val
|
|
|
|
Nothing -> ""
|