@@ -29,9 +29,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2929A simple templating system with variable substitution and conditionals.
3030Example:
3131
32−> > renderTemplate [("name","Sam"),("salary","50,000")] $
33−> "Hi, $name$. $if(salary)$You make $$$salary$.$else$No salary data.$endif$"
34−> > "Hi, John. You make $50,000."
32+> renderTemplate [("name","Sam"),("salary","50,000")] $
33+> "Hi, $name$. $if(salary)$You make $$$salary$.$else$No salary data.$endif$"
34+> "Hi, John. You make $50,000."
3535
3636A slot for an interpolated variable is a variable name surrounded
3737by dollar signs. To include a literal @$@ in your template, use
@@ -48,6 +48,20 @@ is used.
4848
4949Conditional keywords should not be indented, or unexpected spacing
5050problems may occur.
51+
52+If a variable name is associated with multiple values in the association
53+list passed to 'renderTemplate', you may use the @$for$@ keyword to
54+iterate over them:
55+
56+> renderTemplate [("name","Sam"),("name","Joe")] $
57+> "$for(name)$\nHi, $name$.\n$endfor$"
58+> "Hi, Sam.\nHi, Joe."
59+
60+You may optionally specify separators using @$sep$@:
61+
62+> renderTemplate [("name","Sam"),("name","Joe"),("name","Lynn")] $
63+> "Hi, $for(name)$$name$$sep$, $endfor$"
64+> "Hi, Sam, Joe, Lynn."
5165-}
5266
5367module Text.Pandoc.Templates ( renderTemplate
@@ -55,11 +69,11 @@ module Text.Pandoc.Templates ( renderTemplate
5569 , getDefaultTemplate) where
5670
5771import Text.ParserCombinators.Parsec
58−import Control.Monad (liftM, when)
72+import Control.Monad (liftM, when, forM)
5973import qualified Control.Exception as E (try, IOException)
6074import System.FilePath
6175import Text.Pandoc.Shared (readDataFile)
62−import Data.List (intercalate)
76+import Data.List (intercalate, intersperse)
6377import Text.PrettyPrint (text, Doc)
6478import Text.XHtml (primHtml, Html)
6579import Data.ByteString.Lazy.UTF8 (ByteString, fromString)
@@ -111,11 +125,11 @@ renderTemplate vals templ =
111125 Right r -> toTarget $ concat r
112126
113127reservedWords :: [String]
114−reservedWords = ["else","endif"]
128+reservedWords = ["else","endif","for","endfor","sep"]
115129
116130parseTemplate :: GenParser Char TemplateState [String]
117131parseTemplate =
118− many $ (plaintext <|> escapedDollar <|> conditional <|> variable)
132+ many $ (plaintext <|> escapedDollar <|> conditional <|> for <|> variable)
119133 >>= adjustPosition
120134
121135plaintext :: GenParser Char TemplateState String
@@ -124,9 +138,11 @@ plaintext = many1 $ noneOf "$"
124138escapedDollar :: GenParser Char TemplateState String
125139escapedDollar = try $ string "$$" >> return "$"
126140
141+skipEndline :: GenParser Char st ()
142+skipEndline = try $ skipMany (oneOf " \t") >> newline >> return ()
143+
127144conditional :: GenParser Char TemplateState String
128145conditional = try $ do
129− let skipEndline = try $ skipMany (oneOf " \t") >> newline
130146 TemplateState pos vars <- getState
131147 string "$if("
132148 id' <- ident
@@ -149,6 +165,29 @@ conditional = try $ do
149165 when multiline $ optional skipEndline
150166 return contents
151167
168+for :: GenParser Char TemplateState String
169+for = try $ do
170+ TemplateState pos vars <- getState
171+ string "$for("
172+ id' <- ident
173+ string ")$"
174+ -- if newline after the "if", then a newline after "endif" will be swallowed
175+ multiline <- option False $ try $ skipEndline >> return True
176+ let matches = filter (\(k,_) -> k == id') vars
177+ let indent = replicate pos ' '
178+ contents <- forM matches $ \m -> do
179+ updateState $ \(TemplateState p v) -> TemplateState p (m:v)
180+ raw <- liftM concat $ lookAhead parseTemplate
181+ return $ intercalate ('\n':indent) $ lines $ raw ++ "\n"
182+ parseTemplate
183+ sep <- option "" $ do try (string "$sep$")
184+ when multiline $ optional skipEndline
185+ liftM concat parseTemplate
186+ string "$endfor$"
187+ when multiline $ optional skipEndline
188+ setState $ TemplateState pos vars
189+ return $ concat $ intersperse sep contents
190+
152191ident :: GenParser Char TemplateState String
153192ident = do
154193 first <- letter