% % Halipeto -- Haskell static web page generator % Copyright 2004 Andrew Cooke (andrew@acooke.org) % Copyright 2007-2010 Peter Simons (simons@cryp.to) % % 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 % % EXCEPT % % Files in FromHaxml are from HaXml - http://www.cs.york.ac.uk/HaXml - % see the COPYRIGHT and LICENSE in that directory. The files included % are a subset of the full HaXml distribution and have been modified to % originate from the FromHaxml module (so that install on Win32 is % easy). % \section{Functions} This section describes the Custom Functions available by default in the hal namespace. \begin{code} module Halipeto.Functions ( split, parse, eval, attribute, text, textReplace, textAfter, repeat, addDefaultsFn, parseElements, parseElement, mkElements, mkElement, xhtml, element ) where import Prelude hiding (repeat) import Halipeto.Template import Halipeto.Dictionary import Halipeto.Utilities import Data.Char import Text.XML.HaXml.Parse import Text.XML.HaXml.Types \end{code} \subsection{Argument Lists} A function may require more than one argument, but attributes have only a single value. The convention used here is for a fixed number of space delimited arguments, with the possibility of spaces in the final value (usually text). %%Haddock: Separate the argument into the expected number of values \begin{code} split :: Int -> String -> [String] split n s = case split' n [] "" (dropSpace s) of Just ss -> ss Nothing -> error $ "too few arguments (<" ++ (show n) ++ "): " ++ s split' :: Int -> [String] -> String -> String -> Maybe [String] split' _ _ _ "" = Nothing split' 1 l _ s = Just $ l ++ [dropSpace s] split' n l a (c:s) | isSpace c = split' (n-1) (l++[a]) "" (dropSpace s) | otherwise = split' n l (a++[c]) s \end{code} \subsection{Argument Parsing} I had to decide whether to do substittion before or after splitting an argument. If it introduces spaces then early substitution may alter how the argument is split. While this might have lead to cool meta-programming hacks, I thought it more likely to confuse. So substitution is later. The only danger with this that I can see is that one might incorrectly assume that initial arguments, after parsing and substitution, never contain embedded spaces. %%Haddock: Separate argument and do substituion from state \begin{code} parse :: SubDictionary d => d String -> Int -> String -> [String] parse d n = subAll d . split n \end{code} \subsection{hal:eval} Evaluate a function. Probably pointless, but I can't resist the temptation. All substitution takes place during the first evaluation. \begin{verbatim} -> -> ... -> -> \end{verbatim} %%Haddock: Evaluate a named function \begin{code} eval :: (SubDictionary s, Dictionary f (CustomFn s f)) => CustomFn s f eval ctx arg = case search' (funcs ctx) nm of Nothing -> return $ error $ "cannot find function " ++ nm Just fn -> fn ctx val where [nm,val] = parse (state ctx) 2 arg \end{code} \subsection{hal:attribute} Add an attribute to the current element. \begin{verbatim} -> \end{verbatim} %%Haddock: Add an attribute to the current element \begin{code} attribute :: (SubDictionary s, Dictionary f (CustomFn s f)) => CustomFn s f attribute ctx arg = do return (ctx, Attr nm val) where [nm, val] = parse (state ctx) 2 arg \end{code} \subsection{hal:text} Append / prepend text to the contents of the current element. \begin{verbatim} -> value \end{verbatim} \begin{code} text' :: (SubDictionary s, Dictionary f (CustomFn s f)) => Position -> CustomFn s f text' pos ctx arg = do return (ctx, Text pos $ substitute (state ctx) arg) \end{code} %%Haddock: Prepend text to the current element \begin{code} text :: (SubDictionary s, Dictionary f (CustomFn s f)) => CustomFn s f text = text' Before \end{code} %%Haddock: Append text to the current element \begin{code} textAfter :: (SubDictionary s, Dictionary f (CustomFn s f)) => CustomFn s f textAfter = text' After \end{code} %%Haddock: Replace text to the current element \begin{code} textReplace :: (SubDictionary s, Dictionary f (CustomFn s f)) => CustomFn s f textReplace = text' Replace \end{code} \subsection{hal:repeat} Assign the sub-elements of root (in the state dictionary) to name, one at a time, evaluating the template sub--tree. \begin{verbatim} root.1 = a root.2 = b

->

a

b

\end{verbatim} %%Haddock: Repeat the evaluation of the remaining attributes and %%Haddock: contents while the related function returns a Return %%Haddock: value \begin{code} repeat :: (SubDictionary s, Dictionary f (CustomFn s f)) => CustomFn s f repeat ctx arg = repeat' nm vals ctx "" where dct = state ctx [nm, val] = parse dct 2 arg vals = children' dct val repeat' :: (SubDictionary s, Dictionary f (CustomFn s f)) => String -> [s String] -> CustomFn s f repeat' p [] ctx _ = do putStrLn $ "end of repeat: " ++ p return (ctx, Skip) repeat' p (s:ss) ctx _ = do putStrLn $ "repeat: " ++ p ++ ": " ++ (show (contents s)) return (ctx', Repeat $ repeat' p ss) where ctx' = ctx {state = adopt' (state ctx) (p, s)} \end{code} \subsection{hal:eq, hal:neq} Test for (non--)equality of the two arguments. \begin{verbatim} foo -> \end{verbatim} %%Haddock: Continue evaluation of contents if two arguments equal \begin{code} eq :: (SubDictionary s, Dictionary f (CustomFn s f)) => CustomFn s f eq ctx arg = eq' ctx True $ parse (state ctx) 2 arg \end{code} %%Haddock: Continue evaluation of contents if two arguments inequal \begin{code} neq :: (SubDictionary s, Dictionary f (CustomFn s f)) => CustomFn s f neq ctx arg = eq' ctx False $ parse (state ctx) 2 arg eq' :: (SubDictionary s, Dictionary f (CustomFn s f)) => Context s f -> Bool -> [String] -> IO (Context s f, Result s f) eq' ctx x (a:[b]) = do return $ (ctx, if x `xor` (a /= b) then Continue else Skip) where p `xor` q = (p && not q) || (q && not p) \end{code} \subsection{Support for Insertion} These functions provide basic support for meta-templates. You can place template HTML in the database then insert it in a ``skeleton'' template. They're also useful for writing small ``abbreviation'' functions. %%Haddock: Parse text as XML \begin{code} parseElements :: String -> [Element] parseElements txt = fromElement $ parseElement "parseelements" txt where fromElement (Elem "parseelements" _ els) = map unContent els unContent (CElem x) = x unContent s@(CString _ _) = Elem "p" [] [s] unContent _ = error "cannot parse xml as element" \end{code} %%Haddock: Parse text as XML within an element \begin{code} parseElement :: String -> String -> Element parseElement elt txt = fromDoc $ xmlParse txt txt' where txt' = "<" ++ elt ++ ">" ++ txt ++ "" fromDoc (Document _ _ el _) = el \end{code} %%Haddock: Generate a function that inserts XML parsed from text \begin{code} mkElements :: Position -> String -> CustomFn s f mkElements pos txt ctx _ = do return (ctx, Xml pos (parseElements txt)) \end{code} %%Haddock: Generate a function that inserts XML parsed from text, %%Haddock: within an element \begin{code} mkElement :: Position -> String -> String -> CustomFn s f mkElement pos elt txt ctx _ = do return (ctx, Xml pos [parseElement elt txt]) \end{code} \subsection{hal:xhtml} Insert a group of elements (bare text is enclosed in p tags). \begin{verbatim} link = foo text = hello world ->

hello

world
\end{verbatim} %%Haddock: A custom function for inserting XHTML \begin{code} xhtml :: (SubDictionary s, Dictionary f (CustomFn s f)) => Position -> CustomFn s f xhtml pos ctx txt = do return (ctx, Xml pos (parseElements txt')) where txt' = substitute (state ctx) txt \end{code} \subsection{hal:element} Insert a named element. \begin{verbatim} link = foo text = hello world ->

hello world

\end{verbatim} %%Haddock: A custom function for inserting XHTML within an element \begin{code} element :: (SubDictionary s, Dictionary f (CustomFn s f)) => Position -> CustomFn s f element pos ctx txt = do return (ctx, Xml pos [parseElement tag txt'']) where [tag, txt'] = split 2 txt txt'' = substitute (state ctx) txt' \end{code} For an example of the element tag in use, see the image.html teplate in the demo. Insertion of a p element allows the body text to contain template functions without the need for explicit $<$p$>$ markup around each paragraph. \subsection{Load Defaults} Make the custom functions above available in the funcs dictionary under the hal namespace. %%Haddock: Add standard functions to a dictionary \begin{code} addDefaultsFn :: (SubDictionary s, Dictionary f (CustomFn s f)) => f (CustomFn s f) -> f (CustomFn s f) addDefaultsFn fn = addAll fn fns where fns = map (\(n,f) -> ([hal, n], f)) lst lst = [("eval", eval), ("attribute", attribute), ("text", text), ("textafter", textAfter), ("textreplace", textReplace), ("repeat", repeat), ("eq", eq), ("neq", neq), ("xhtml", xhtml Before), ("xhtmlafter", xhtml After), ("xhtmlreplace", xhtml Replace), ("element", element Before), ("elementafter", element After), ("elementreplace", element Replace)] \end{code}